Merge branch 'master' into tw2014

This commit is contained in:
Axel Uhl committed 2014-07-25 00:49:54 +02:00
commit ce8ee1e46f
50 files changed
+1158 -2306

No files matched your search

@@ -0,0 +1,13 @@
# Operating Igtimi WindBots
For wind measurements we're trying to consistently use Igtimi WindBot equipment. The AdminConsole has a tab "Igtimi Accounts" where accounts for the Igtimi YachtBot web site can be managed. WindBots associated with any of these accounts will feed their wind readings, when active, into races being tracked. Operations of the WindBots mainly needs to take care of charging the devices, turning them on when they leave the harbor and turning them off when they leave the race course. This can also be delegated to race officials carrying these devices on their vessels.
The devices have three LEDs whose status works as follows:
1. The three LED’s are “power”, “connection”, and “GPS” in order from top to bottom.
2. All the lights should be on when operating normally.
3. Slow flashing is normal while booting.
4. Fast flashing means that function can’t start or has failed - e.g. SIM not inserted. i.e. you shouldn’t see this
5. During sync, the “power” light blips and the “connection” LED should stay on until the sync has finished.
More information on running the WindBots can be found [here](https://igtimi.desk.com/customer/portal/articles/1494533-yachtbot-tracker-basic-operation?b_id=1690).
+1
View File
@@ -15,6 +15,7 @@ Like businesses, sailors need the latest information to make strategic decisions
* [[EC2 Backup Strategy|wiki/amazon-ec2-backup-strategy]]
* [[TracTrac|wiki/tractrac-lifecycle]]
* [[Analytics on a stick|wiki/analytics-on-a-stick]]
* [[Operating Igtimi WindBots/windbot-operations]]
* [[Monitoring Apache and RabbitMQ|wiki/monitoring-apache-and-rabbitmq]]
* [[JMX Support|wiki/jmx]]
* Information for Developers
@@ -185,42 +185,47 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
Speed aws = getAWS(timePoint, awsPair);
com.sap.sse.common.Util.Pair<GpsLatLong, GpsLatLong> gpsPair = getSurroundingFixes(getGpsTrack(deviceSerialNumber), timePoint);
Position pos = getPosition(timePoint, gpsPair);
com.sap.sse.common.Util.Pair<HDG, HDG> hdgPair = getSurroundingFixes(getHdgTrack(deviceSerialNumber), timePoint);
com.sap.sse.common.Util.Pair<HDGM, HDGM> hdgmPair = getSurroundingFixes(getHdgmTrack(deviceSerialNumber), timePoint);
Bearing heading = getHeading(timePoint, hdgPair, hdgmPair, pos);
if (awa != null && aws != null && pos != null && heading != null) {
Bearing apparentWindDirection = heading.add(awa);
SpeedWithBearing apparentWindSpeedWithDirection = new KnotSpeedWithBearingImpl(aws.getKnots(), apparentWindDirection);
/*
* Hint from Brent Russell from Igtimi, at 2013-12-05 on the question whether to use GpsLatLong to improve
* precision of boat speed / coarse over SOG/COG measurements:
*
* "Personally I would use COG/SOG exclusively, and if unhappy with the result add a small amount of
* smoothing and consider dropping samples as outliers if they cause a SOG discontinuity. The latter might
* happen as a satellite is dropped/acquired - and I'd expect to see a time correlated position jump as
* well. Probably not though a direction/speed correlation :)
*
* All our GPS systems are using Doppler to calculate COG/SOG and this should be the most accurate measure.
* I don't believe that delta position really adds any more "truth" to the measurement of physical reality,
* if that makes sense. I'd trust d-p even less at low speeds, where you'll see the most disagreement. Also
* there is a significant quantisation noise error in the d-p calculations from the GPS resolution too, so
* you'd have to smooth it before averaging - possibly in a speed dependent way.
*
* Remember that Doppler COG/SOG is using the same raw satellite measurements that are being used to
* calculate position, just the algorithm is different. I suspect that merging the two might be, in
* practice, just a slightly indirect way of averaging. If you like the central limit theorem in action over
* the set of algorithms!
*
* So again, my personal preference would be to work with the data that should be the most accurate
* (COG/SOG) and consider algorithms that handle smoothing of that data best."
*/
com.sap.sse.common.Util.Pair<SOG, SOG> sogPair = getSurroundingFixes(getSogTrack(deviceSerialNumber), timePoint);
Speed sog = getSOG(timePoint, sogPair);
com.sap.sse.common.Util.Pair<COG, COG> cogPair = getSurroundingFixes(getCogTrack(deviceSerialNumber), timePoint);
Bearing cog = getCOG(timePoint, cogPair);
SpeedWithBearing sogCog = new KnotSpeedWithBearingImpl(sog.getKnots(), cog);
SpeedWithBearing trueWindSpeedAndDirection = apparentWindSpeedWithDirection.add(sogCog);
result = new WindImpl(pos, timePoint, trueWindSpeedAndDirection);
if (pos != null) {
com.sap.sse.common.Util.Pair<HDG, HDG> hdgPair = getSurroundingFixes(getHdgTrack(deviceSerialNumber), timePoint);
com.sap.sse.common.Util.Pair<HDGM, HDGM> hdgmPair = getSurroundingFixes(getHdgmTrack(deviceSerialNumber), timePoint);
Bearing heading = getHeading(timePoint, hdgPair, hdgmPair, pos);
if (awa != null && aws != null && heading != null) {
Bearing apparentWindDirection = heading.add(awa);
SpeedWithBearing apparentWindSpeedWithDirection = new KnotSpeedWithBearingImpl(aws.getKnots(), apparentWindDirection);
/*
* Hint from Brent Russell from Igtimi, at 2013-12-05 on the question whether to use GpsLatLong to
* improve precision of boat speed / coarse over SOG/COG measurements:
*
* "Personally I would use COG/SOG exclusively, and if unhappy with the result add a small amount of
* smoothing and consider dropping samples as outliers if they cause a SOG discontinuity. The latter
* might happen as a satellite is dropped/acquired - and I'd expect to see a time correlated position
* jump as well. Probably not though a direction/speed correlation :)
*
* All our GPS systems are using Doppler to calculate COG/SOG and this should be the most accurate
* measure. I don't believe that delta position really adds any more "truth" to the measurement of
* physical reality, if that makes sense. I'd trust d-p even less at low speeds, where you'll see the
* most disagreement. Also there is a significant quantisation noise error in the d-p calculations from
* the GPS resolution too, so you'd have to smooth it before averaging - possibly in a speed dependent
* way.
*
* Remember that Doppler COG/SOG is using the same raw satellite measurements that are being used to
* calculate position, just the algorithm is different. I suspect that merging the two might be, in
* practice, just a slightly indirect way of averaging. If you like the central limit theorem in action
* over the set of algorithms!
*
* So again, my personal preference would be to work with the data that should be the most accurate
* (COG/SOG) and consider algorithms that handle smoothing of that data best."
*/
com.sap.sse.common.Util.Pair<SOG, SOG> sogPair = getSurroundingFixes(getSogTrack(deviceSerialNumber), timePoint);
Speed sog = getSOG(timePoint, sogPair);
com.sap.sse.common.Util.Pair<COG, COG> cogPair = getSurroundingFixes(getCogTrack(deviceSerialNumber), timePoint);
Bearing cog = getCOG(timePoint, cogPair);
SpeedWithBearing sogCog = new KnotSpeedWithBearingImpl(sog.getKnots(), cog);
SpeedWithBearing trueWindSpeedAndDirection = apparentWindSpeedWithDirection.add(sogCog);
result = new WindImpl(pos, timePoint, trueWindSpeedAndDirection);
} else {
result = null;
}
} else {
result = null;
}
@@ -14,8 +14,11 @@ import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
@@ -72,6 +75,39 @@ public class LeaderboardDTODiffingTest {
}
return null;
}
@Test
public void testEmptyLegDetailsGetFilled() throws FileNotFoundException, IOException, ClassNotFoundException {
// remove legDetails for R9 for all competitors from previousVersion; remember which ones did contain data
Set<CompetitorDTO> hadLegDetailsInR9 = new HashSet<>();
Map<CompetitorDTO, LeaderboardRowDTO> newRows = new HashMap<>();
for (CompetitorDTO competitor : previousVersion.rows.keySet()) {
LeaderboardRowDTO competitorRow = new LeaderboardRowDTO();
cloner.clone(newVersion.rows.get(competitor), competitorRow);
Map<String, LeaderboardEntryDTO> newFieldsByRaceColumnName = new HashMap<>();
for (Entry<String, LeaderboardEntryDTO> e : competitorRow.fieldsByRaceColumnName.entrySet()) {
LeaderboardEntryDTO newEntry = new LeaderboardEntryDTO();
cloner.clone(e.getValue(), newEntry);
newFieldsByRaceColumnName.put(e.getKey(), newEntry);
}
competitorRow.fieldsByRaceColumnName = newFieldsByRaceColumnName;
newRows.put(competitor, competitorRow); // use a copy of the row so we can null out legDetails for R9 in previousVersion only
LeaderboardRowDTO previousRow = previousVersion.rows.get(competitor);
assertNotSame(previousRow, competitorRow);
if (previousRow.fieldsByRaceColumnName.get("R9").legDetails != null) {
hadLegDetailsInR9.add(competitor);
previousRow.fieldsByRaceColumnName.get("R9").legDetails = null;
assertNotNull(competitorRow.fieldsByRaceColumnName.get("R9").legDetails != null);
}
}
newVersion.rows = newRows;
newVersion.strip(previousVersion); // should contain the leg details now
LeaderboardDTO applied = newVersion.getLeaderboardDTO(previousVersion);
// now applied must contain the leg details
for (CompetitorDTO competitor : hadLegDetailsInR9) {
assertNotNull(applied.rows.get(competitor).fieldsByRaceColumnName.get("R9").legDetails);
}
}
@Test
public void testLeaderboardSuccessfullyRead() {
@@ -397,7 +397,7 @@ public interface TrackedRace extends Serializable {
/**
* Computes a default wind direction based on the direction of the first leg at time <code>at</code>, with a default
* speed of one knot. Note that this wind direction can only be used if {@link #raceIsKnownToStartUpwind()} returns
* speed of zero knots. Note that this wind direction can only be used if {@link #raceIsKnownToStartUpwind()} returns
* <code>true</code>.
*
* @param at
@@ -1378,7 +1378,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
Position firstLegEnd = getApproximatePosition(firstLeg.getTo(), at);
Position firstLegStart = getApproximatePosition(firstLeg.getFrom(), at);
if (firstLegStart != null && firstLegEnd != null) {
result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(1.0,
result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(0.0,
firstLegEnd.getBearingGreatCircle(firstLegStart)));
} else {
result = null;
@@ -883,7 +883,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
}
lastBoatFix = getBoatFix(visibleLeaderInfo.getB(), date);
}
if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null) {
if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null && lastBoatFix.speedWithBearing != null) {
LegInfoDTO legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1);
double advantageLineLengthInKm = 1.0; // TODO this should probably rather scale with the visible
// area of the map; bug 616
@@ -1587,7 +1587,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
multiply(factorForAfter)).divide(1).getDegrees();
}
SpeedWithBearingDTO betweenSpeed = new SpeedWithBearingDTO(
factorForBefore*fixBefore.speedWithBearing.speedInKnots + factorForAfter*fixAfter.speedWithBearing.speedInKnots,
factorForBefore*(fixBefore.speedWithBearing==null?0:fixBefore.speedWithBearing.speedInKnots) +
factorForAfter*(fixAfter.speedWithBearing==null?0:fixAfter.speedWithBearing.speedInKnots),
betweenBearing);
result = new GPSFixDTO(date, betweenPosition, betweenSpeed, closer.degreesBoatToTheWind,
closer.tack, closer.legType, fixBefore.extrapolated || fixAfter.extrapolated);
@@ -348,11 +348,10 @@ public class LegColumn extends ExpandableSortableColumn<String> {
@Override
public String getValue(LeaderboardRowDTO row) {
LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName);
int legDetailsCount = leaderboardEntryDTO.legDetails.size();
LegEntryDTO legEntry = getLegEntry(row);
if (legEntry != null && legEntry.rank != 0) {
return ""+legEntry.rank;
} else if (legIndex+1 > legDetailsCount) {
} else if (leaderboardEntryDTO.legDetails != null && legIndex+1 > leaderboardEntryDTO.legDetails.size()) {
return "n/a";
} else {
return "";
@@ -55,6 +55,7 @@ import com.sap.sailing.gwt.ui.simulator.windpattern.WindPatternDisplayManager;
import com.sap.sailing.gwt.ui.simulator.windpattern.WindPatternNotFoundException;
import com.sap.sailing.gwt.ui.simulator.windpattern.WindPatternSetting;
import com.sap.sailing.simulator.BoatClassProperties;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.PolarDiagram;
import com.sap.sailing.simulator.SailingSimulator;
@@ -62,11 +63,11 @@ import com.sap.sailing.simulator.SimulationParameters;
import com.sap.sailing.simulator.TimedPosition;
import com.sap.sailing.simulator.TimedPositionWithSpeed;
import com.sap.sailing.simulator.impl.ConfigurationManager;
import com.sap.sailing.simulator.impl.CurvedGrid;
import com.sap.sailing.simulator.impl.PathGenerator1Turner;
import com.sap.sailing.simulator.impl.PathImpl;
import com.sap.sailing.simulator.impl.PolarDiagramCSV;
import com.sap.sailing.simulator.impl.ReadingConfigurationFileStatus;
import com.sap.sailing.simulator.impl.RectangularBoundary;
import com.sap.sailing.simulator.impl.SailingSimulatorImpl;
import com.sap.sailing.simulator.impl.SimulationParametersImpl;
import com.sap.sailing.simulator.impl.TimedPositionImpl;
@@ -185,9 +186,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula
course.add(nw);
course.add(se);
RectangularBoundary bd = new RectangularBoundary(nw, se, 0.1);
// List<Position> lattice = bd.extractLattice(params.getxRes(),
// params.getyRes());
Grid bd = new CurvedGrid(nw, se);
controlParameters.resetBlastRandomStream = params.isKeepState();
retreiveWindControlParameters(pattern);
@@ -201,7 +200,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula
throw new WindPatternNotFoundException("Please select a valid wind pattern.");
}
Position[][] grid = bd.extractGrid(params.getxRes(), params.getyRes(), params.getBorderY(), params.getBorderX());
Position[][] grid = bd.generatePositions(params.getxRes(), params.getyRes(), params.getBorderY(), params.getBorderX());
wf.setPositionGrid(grid);
TimePoint startTime = new MillisecondsTimePoint(params.getStartTime().getTime());
@@ -549,9 +548,9 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula
SailingSimulator sailingSimulator = new SailingSimulatorImpl(simulationParameters);
Path gpsWind = sailingSimulator.getLegGPSTrack(SimulatorServiceUtils.toSimulatorUISelection(requestData.selection));
RectangularBoundary rectangularBoundry = new RectangularBoundary(oldMovedPosition, newMovedPosition, 0.1);
this.controlParameters.baseWindBearing += rectangularBoundry.getSouth().getDegrees();
WindFieldGeneratorMeasured windFieldGenerator = new WindFieldGeneratorMeasured(rectangularBoundry, this.controlParameters);
Grid grid = new CurvedGrid(oldMovedPosition, newMovedPosition);
this.controlParameters.baseWindBearing += grid.getSouth().getDegrees();
WindFieldGeneratorMeasured windFieldGenerator = new WindFieldGeneratorMeasured(grid, this.controlParameters);
windFieldGenerator.setGPSWind(gpsWind);
TimePoint startTime = new MillisecondsTimePoint(requestData.oldMovedPointTimePoint);
@@ -220,7 +220,7 @@ public class SimulatorField implements VectorField {
private String[] createColorsForSpeeds() {
String[] colors = new String[256];
double alphaMin = 0.0;
double alphaMax = 1.0;
double alphaMax = 0.9;
int greyValue = 255;
for (int i = 0; i < 256; i++) {
colors[i] = "rgba(" + (greyValue) + "," + (greyValue) + "," + (greyValue) + ","
@@ -156,7 +156,7 @@ public class WindInfoForRaceVectorField implements VectorField, AverageLatitudeP
public double getMotionScale(int zoomLevel) {
// This implementation is copied from SimulatorField, hoping it does something useful in combination with
// the Swarm implementation.
return 0.07 * Math.pow(1.6, Math.min(1.0, 6.0 - zoomLevel));
return 0.05 * Math.pow(1.6, Math.min(1.0, 6.0 - zoomLevel));
}
@Override
@@ -170,7 +170,7 @@ public class WindInfoForRaceVectorField implements VectorField, AverageLatitudeP
*/
@Override
public double getLineWidth(double speed) {
return 3.*speed/MAX_WIND_SPEED_IN_KNOTS;
return 1.2;
}
@Override
@@ -180,7 +180,7 @@ public class WindInfoForRaceVectorField implements VectorField, AverageLatitudeP
@Override
public double getParticleFactor() {
return 1.0;
return 0.5;
}
/**
@@ -188,7 +188,7 @@ public class WindInfoForRaceVectorField implements VectorField, AverageLatitudeP
*/
@Override
public String getColor(double speed) {
return "rgba(255,255,255,"+Math.min(255, Math.round(255.*speed/MAX_WIND_SPEED_IN_KNOTS))+")";
return "rgba(255,255,255,"+Math.min(1.0, 0.4+0.8*speed/MAX_WIND_SPEED_IN_KNOTS)+")";
}
}
@@ -6,8 +6,8 @@ import org.junit.Test;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.impl.RectangularBoundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.impl.RectangularGrid;
public class RectangularBoundaryTest {
@@ -16,8 +16,8 @@ public class RectangularBoundaryTest {
Position p1 = new DegreePosition(25.661333, -90.752563);
Position p2 = new DegreePosition(24.522137, -90.774536);
Boundary b = new RectangularBoundary(p1, p2, 0.1);
Position[][] grid = b.extractGrid(20,20,0,0);
Grid b = new RectangularGrid(p1, p2);
Position[][] grid = b.generatePositions(20,20,0,0);
assertEquals("Number of lattice points",400,grid.length*grid[0].length);
}
@@ -20,7 +20,7 @@ import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.PolarDiagram;
import com.sap.sailing.simulator.SimulationParameters;
import com.sap.sailing.simulator.impl.PolarDiagram49STG;
import com.sap.sailing.simulator.impl.RectangularBoundary;
import com.sap.sailing.simulator.impl.RectangularGrid;
import com.sap.sailing.simulator.impl.SailingSimulatorImpl;
import com.sap.sailing.simulator.impl.SimulationParametersImpl;
import com.sap.sailing.simulator.util.SailingSimulatorConstants;
@@ -43,8 +43,8 @@ public class SimulatorTest {
course.add(end);
PolarDiagram pd = new PolarDiagram49STG();//PolarDiagram49.CreateStandard49();
RectangularBoundary bd = new RectangularBoundary(start, end, 0.1);
Position[][] positions = bd.extractGrid(10, 10, 0, 0);
RectangularGrid bd = new RectangularGrid(start, end);
Position[][] positions = bd.generatePositions(10, 10, 0, 0);
Bearing windBear = end.getBearingGreatCircle(start);
WindControlParameters windParameters = new WindControlParameters(12.0, windBear.getDegrees());
WindFieldGenerator wf = new WindFieldGeneratorBlastImpl(bd, windParameters);
@@ -20,9 +20,9 @@ import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.PolarDiagram;
import com.sap.sailing.simulator.SimulationParameters;
import com.sap.sailing.simulator.impl.PathGeneratorTreeGrowWind3;
import com.sap.sailing.simulator.impl.PathGeneratorTreeGrowWind;
import com.sap.sailing.simulator.impl.PolarDiagram49STG;
import com.sap.sailing.simulator.impl.RectangularBoundary;
import com.sap.sailing.simulator.impl.RectangularGrid;
import com.sap.sailing.simulator.impl.SimulationParametersImpl;
import com.sap.sailing.simulator.util.SailingSimulatorConstants;
import com.sap.sailing.simulator.windfield.WindControlParameters;
@@ -44,8 +44,8 @@ public class TreeGrowTest {
course.add(start);
course.add(end);
PolarDiagram pd = new PolarDiagram49STG();//PolarDiagram49.CreateStandard49();
RectangularBoundary bd = new RectangularBoundary(start, end, 0.1);
Position[][] positions = bd.extractGrid(10, 10, 0, 0);
RectangularGrid bd = new RectangularGrid(start, end);
Position[][] positions = bd.generatePositions(10, 10, 0, 0);
//RectangularBoundary new_bd = new RectangularBoundary(start, end, 0.1);
//Speed knotSpeed = new KnotSpeedImpl(8);
WindControlParameters windParameters = new WindControlParameters(12, start.getBearingGreatCircle(end).reverse().getDegrees());
@@ -62,7 +62,7 @@ public class TreeGrowTest {
param.setProperty("Djikstra.gridv[int]", 10.0);
param.setProperty("Djikstra.gridh[int]", 100.0);*/
PathGeneratorTreeGrowWind3 treeGrow = new PathGeneratorTreeGrowWind3(param);
PathGeneratorTreeGrowWind treeGrow = new PathGeneratorTreeGrowWind(param);
Path path = treeGrow.getPath();
@@ -23,7 +23,7 @@ import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.simulator.impl.RectangularBoundary;
import com.sap.sailing.simulator.impl.RectangularGrid;
import com.sap.sailing.simulator.impl.TimedPositionWithSpeedImpl;
import com.sap.sailing.simulator.windfield.WindControlParameters;
import com.sap.sailing.simulator.windfield.impl.WindFieldGeneratorBlastImpl;
@@ -51,11 +51,11 @@ public class WindFieldGeneratorTest {
course.add(end);
WindControlParameters windParameters = new WindControlParameters(3, 180);
RectangularBoundary bd = new RectangularBoundary(start, end, 0.1);
RectangularGrid bd = new RectangularGrid(start, end);
WindFieldGeneratorImpl wf = new WindFieldGeneratorBlastImpl(bd, windParameters);
int hSteps = 10;
int vSteps = 5;
Position[][] positions = bd.extractGrid(hSteps, vSteps, 0, 0);
Position[][] positions = bd.generatePositions(hSteps, vSteps, 0, 0);
assert (positions.length*positions[0].length == hSteps * vSteps);
int index = 0;
for (int i = 0; i < positions.length; ++i) {
@@ -63,7 +63,7 @@ public class WindFieldGeneratorTest {
logger.info("P" + ++index + ":" + positions[i][j]);
}
}
wf.setPositionGrid(bd.extractGrid(hSteps, vSteps, 0, 0));
wf.setPositionGrid(bd.generatePositions(hSteps, vSteps, 0, 0));
Position[][] positionGrid = wf.getPositionGrid();
assertNotNull("Position Grid is not null", positionGrid);
assertEquals("Position Grid Number of Rows", vSteps, positionGrid.length);
@@ -91,13 +91,13 @@ public class WindFieldGeneratorTest {
windParameters.frequency = 0.375;
windParameters.amplitude = 20.0;
RectangularBoundary bd = new RectangularBoundary(start, end, 0.1);
RectangularGrid bd = new RectangularGrid(start, end);
WindFieldGeneratorOscillationImpl wf = new WindFieldGeneratorOscillationImpl(bd, windParameters);
int hSteps = 30;
int vSteps = 15;
wf.setPositionGrid(bd.extractGrid(hSteps, vSteps, 0, 0));
wf.setPositionGrid(bd.generatePositions(hSteps, vSteps, 0, 0));
Position[][] positionGrid = wf.getPositionGrid();
TimePoint startTime = new MillisecondsTimePoint(0);
Duration timeStep = new MillisecondsDurationImpl(30 * 1000);
@@ -235,12 +235,12 @@ public class WindFieldGeneratorTest {
windParameters.blastWindSpeed = 0.0;
windParameters.blastWindSpeedVar = 0.0;
RectangularBoundary bd = new RectangularBoundary(start, end, 0.1);
RectangularGrid bd = new RectangularGrid(start, end);
WindFieldGeneratorCombined wf = new WindFieldGeneratorCombined(bd, windParameters);
int hSteps = 30;
int vSteps = 15;
wf.setPositionGrid(bd.extractGrid(hSteps, vSteps, 0, 0));
wf.setPositionGrid(bd.generatePositions(hSteps, vSteps, 0, 0));
Position[][] positionGrid = wf.getPositionGrid();
TimePoint startTime = new MillisecondsTimePoint(0);
Duration timeStep = new MillisecondsDurationImpl(30 * 1000);
@@ -1,46 +0,0 @@
package com.sap.sailing.simulator;
import java.io.Serializable;
import java.util.Map;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sse.common.Util;
public interface Boundary extends Serializable {
static Bearing TRUENORTH = new DegreeBearingImpl(0);
static Bearing TRUESOUTH = new DegreeBearingImpl(180);
static Bearing TRUEEAST = new DegreeBearingImpl(90);
static Bearing TRUEWEST = new DegreeBearingImpl(270);
double getTolerance();
Map<String, Position> getCorners();
boolean isWithinBoundaries(Position P);
//List<Position> extractLattice(int hPoints, int vPoints);
Position[][] extractGrid(int hPoints, int vPoints, int borderY, int borderX);
//List<Position> extractLattice(Distance hStep, Distance vstep);
public Util.Pair<Integer,Integer> getGridIndex(Position x);
public int getResY();
public int getResX();
public int getBorderY();
public int getBorderX();
Bearing getNorth();
Bearing getSouth();
Bearing getEast();
Bearing getWest();
Distance getHeight();
Distance getWidth();
Map<String,Double> getRelativeCoordinates(Position p);
Position getRelativePoint(double x, double y);
}
@@ -0,0 +1,54 @@
package com.sap.sailing.simulator;
import java.io.Serializable;
import java.util.Map;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sse.common.Util;
/**
* The Grid interface defines access to a set of GPS-positions which form a lattice of supporting positions for
* representing a vectorfield or performing optimization.
*
* @author Christopher Ronnewinkel (D036654)
*
*/
public interface Grid extends Serializable {
static Bearing relativeNorth = new DegreeBearingImpl(0);
static Bearing relativeSouth = new DegreeBearingImpl(180);
static Bearing relativeEast = new DegreeBearingImpl(90);
static Bearing relativeWest = new DegreeBearingImpl(270);
Map<String, Position> getCorners();
boolean inBounds(Position P);
Position[][] generatePositions(int hPoints, int vPoints, int borderY, int borderX);
public Util.Pair<Integer, Integer> getIndex(Position x);
public int getResY();
public int getResX();
public int getBorderY();
public int getBorderX();
Bearing getNorth();
Bearing getSouth();
Bearing getEast();
Bearing getWest();
Distance getHeight();
Distance getWidth();
}
@@ -18,7 +18,7 @@ public interface SimulationParameters {
WindFieldGenerator getWindField();
Boundary getBoundaries();
Grid getGrid();
Map<String,Double> getSettings();
@@ -0,0 +1,218 @@
package com.sap.sailing.simulator.impl;
import java.util.HashMap;
import java.util.Map;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.simulator.Grid;
import com.sap.sse.common.Util;
/**
* Implements the {@link Grid} interface by providing a grid of GPS-positions based on great circles and the
* corresponding index-calculation to associate an arbitrary GPS-position with the closest grid-GPS-position.
*
* Since the grid is constructed in spherical coordinates, we call it the curved grid. It stays perfectly symmetric no
* matter along which bearing to North it is aligned with.
*
* @author Christopher Ronnewinkel (D036654)
*
*/
public class CurvedGrid implements Grid {
private static final long serialVersionUID = 3598121983120213464L;
private Position rcStart; // start position of race course
private Position rcEnd; // end position of race course
private int vPoints; // number of vertical steps
private int hPoints; // number of horizontal steps
private int borderY;
private int borderX;
private Position gridNorthWest;
private Position gridSouthEast;
private Position gridSouthWest;
private Position gridNorthEast;
private Bearing gridNorth;
private Bearing gridSouth;
private Bearing gridEast;
private Bearing gridWest;
private Distance gridWidth;
private double xscale = 1.5;
private Distance gridHeight;
public CurvedGrid(Position p1, Position p2) {
rcStart = p1;
rcEnd = p2;
gridNorth = p1.getBearingGreatCircle(p2);
gridSouth = gridNorth.reverse();
gridEast = gridNorth.add(relativeEast);
gridWest = gridNorth.add(relativeWest);
gridHeight = p1.getDistance(p2);
gridWidth = gridHeight.scale(2);
gridNorthWest = p2.translateGreatCircle(gridWest, gridHeight);
gridNorthEast = p2.translateGreatCircle(gridEast, gridHeight);
gridSouthWest = p1.translateGreatCircle(gridWest, gridHeight);
gridSouthEast = p1.translateGreatCircle(gridEast, gridHeight);
}
@Override
public Map<String, Position> getCorners() {
Map<String, Position> map = new HashMap<String, Position>();
map.put("NorthWest", gridNorthWest);
map.put("SouthWest", gridSouthWest);
map.put("SouthEast", gridSouthEast);
map.put("NorthEast", gridNorthEast);
return map;
}
@Override
public boolean inBounds(Position p) {
Position northProjection = p.projectToLineThrough(gridNorthWest, getEast());
Position southProjection = p.projectToLineThrough(gridSouthWest, getEast());
Position westProjection = p.projectToLineThrough(gridNorthWest, getNorth());
Position eastProjection = p.projectToLineThrough(gridNorthEast, getNorth());
Distance northSouth = northProjection.getDistance(southProjection);
Distance eastWest = eastProjection.getDistance(westProjection);
return (northSouth.compareTo(p.getDistance(northProjection)) >= 0)
&& (northSouth.compareTo(p.getDistance(southProjection)) >= 0)
&& (eastWest.compareTo(p.getDistance(eastProjection)) >= 0)
&& (eastWest.compareTo(p.getDistance(westProjection)) >= 0);
}
@Override
public Position[][] generatePositions(int hPoints, int vPoints, int borderY, int borderX) {
this.vPoints = vPoints;
this.hPoints = hPoints;
this.borderY = borderY;
this.borderX = borderX;
Distance vStep = gridHeight.scale(1.0 / (vPoints - 1));
Distance hStep = gridHeight.scale(xscale / (hPoints - 1));
Position[][] grid = new Position[vPoints + 2 * borderY][hPoints + 2 * borderX];
Position pv;
for (int i = -borderY; i < (vPoints + borderY); i++) {
if (i == 0) {
pv = rcStart;
} else if (i == vPoints - 1) {
pv = rcEnd;
} else {
pv = rcStart.translateGreatCircle(gridNorth, vStep.scale(i));
}
int j = 0;
// left side
while (j < (hPoints + 2 * borderX) / 2) {
grid[i + borderY][j] = pv.translateGreatCircle(gridWest,
hStep.scale((hPoints + 2 * borderX - 1) / 2. - j));
j++;
}
// middle
if ((hPoints + 2 * borderX) % 2 == 1) {
grid[i + borderY][j] = pv;
j++;
}
// right side
while (j < (hPoints + 2 * borderX)) {
grid[i + borderY][j] = pv.translateGreatCircle(gridEast,
hStep.scale(j - (hPoints + 2 * borderX - 1) / 2.));
j++;
}
}
return grid;
}
public Util.Pair<Integer, Integer> getIndex(Position x) {
double vFlt = 0;
double hFlt = 0;
if (!x.equals(rcStart)) {
Position h = x.projectToLineThrough(rcStart, gridNorth);
vFlt = vPoints * rcStart.getDistance(h).getMeters() / gridHeight.getMeters();
int sign = +1;
if (Math.abs(h.getBearingGreatCircle(x).getDifferenceTo(gridWest).getDegrees()) < 90.0) {
sign = -1;
}
hFlt = sign * (hPoints - 1) * x.getDistance(h).getMeters() / gridHeight.getMeters() / xscale;
}
int vIdx = Math.min(Math.max(-this.borderY, (int) Math.round(vFlt) + this.borderY), vPoints - 1 + this.borderY);
int hIdx = Math.min(Math.max(-this.borderX, (int) Math.round((hPoints - 1) / 2. + hFlt)), hPoints - 1 + this.borderX);
return new Util.Pair<Integer, Integer>(vIdx, hIdx);
}
@Override
public int getResY() {
return this.vPoints;
}
@Override
public int getResX() {
return this.hPoints;
}
@Override
public int getBorderY() {
return this.borderY;
}
@Override
public int getBorderX() {
return this.borderX;
}
@Override
public Bearing getNorth() {
return gridNorth;
}
@Override
public Bearing getSouth() {
return gridSouth;
}
@Override
public Bearing getEast() {
return gridEast;
}
@Override
public Bearing getWest() {
return gridWest;
}
@Override
public Distance getWidth() {
return gridWidth;
}
@Override
public Distance getHeight() {
return gridHeight;
}
}
@@ -25,14 +25,14 @@ public class PathCandidate implements Comparable<PathCandidate> {
// sort descending by length, width, height
public int compareTo(PathCandidate other) {
if (this.path.length() == other.path.length()) {
if (this.vrt == other.vrt) {
if (this.trn == other.trn) {
if (Math.abs(this.hrz) == Math.abs(other.hrz)) {
return 0;
} else {
return (Math.abs(this.hrz) < Math.abs(other.hrz) ? -1 : +1);
}
} else {
return (this.vrt > other.vrt ? -1 : +1);
return (this.trn < other.trn ? -1 : +1);
}
} else {
return (this.path.length() < other.path.length() ? -1 : +1);
@@ -9,7 +9,7 @@ import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.PolarDiagram;
import com.sap.sailing.simulator.SimulationParameters;
@@ -26,7 +26,7 @@ public class PathGenerator1TurnerLeftDirect extends PathGeneratorBase {
public Path getPath() {
// retrieve simulation parameters
Boundary boundary = new RectangularBoundary(this.parameters.getCourse().get(0), this.parameters
Grid boundary = new RectangularGrid(this.parameters.getCourse().get(0), this.parameters
.getCourse().get(1));// simulationParameters.getBoundaries();
WindFieldGenerator windField = this.parameters.getWindField();
PolarDiagram polarDiagram = this.parameters.getBoatPolarDiagram();
@@ -102,7 +102,7 @@ public class PathGenerator1TurnerLeftDirect extends PathGeneratorBase {
// System.out.println("out of time");
break;
}
if (!boundary.isWithinBoundaries(currentPosition)) {
if (!boundary.inBounds(currentPosition)) {
// outOfBounds = true;
// System.out.println("out of bounds");
break;
@@ -9,7 +9,7 @@ import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.PolarDiagram;
import com.sap.sailing.simulator.SimulationParameters;
@@ -26,7 +26,7 @@ public class PathGenerator1TurnerRightDirect extends PathGeneratorBase {
public Path getPath() {
// retrieve simulation parameters
Boundary boundary = new RectangularBoundary(this.parameters.getCourse().get(0), this.parameters
Grid boundary = new RectangularGrid(this.parameters.getCourse().get(0), this.parameters
.getCourse().get(1));// simulationParameters.getBoundaries();
WindFieldGenerator windField = this.parameters.getWindField();
PolarDiagram polarDiagram = this.parameters.getBoatPolarDiagram();
@@ -102,7 +102,7 @@ public class PathGenerator1TurnerRightDirect extends PathGeneratorBase {
// System.out.println("out of time");
break;
}
if (!boundary.isWithinBoundaries(currentPosition)) {
if (!boundary.inBounds(currentPosition)) {
// outOfBounds = true;
// System.out.println("out of bounds");
break;
@@ -14,7 +14,7 @@ import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.PolarDiagram;
import com.sap.sailing.simulator.SimulationParameters;
@@ -34,7 +34,7 @@ public class PathGeneratorDijkstra extends PathGeneratorBase {
public Path getPath() {
// retrieve simulation parameters
Boundary boundary = new RectangularBoundary(this.parameters.getCourse().get(0), this.parameters
Grid boundary = new RectangularGrid(this.parameters.getCourse().get(0), this.parameters
.getCourse().get(1));// simulationParameters.getBoundaries();
WindFieldGenerator windField = this.parameters.getWindField();
PolarDiagram polarDiagram = this.parameters.getBoatPolarDiagram();
@@ -49,7 +49,7 @@ public class PathGeneratorDijkstra extends PathGeneratorBase {
int gridv = this.parameters.getProperty("Djikstra.gridv[int]").intValue(); // number of vertical grid steps
int gridh = this.parameters.getProperty("Djikstra.gridh[int]").intValue(); // number of horizontal grid
// steps
Position[][] sailGrid = boundary.extractGrid(gridh, gridv, 0, 0);
Position[][] sailGrid = boundary.generatePositions(gridh, gridv, 0, 0);
// create adjacency graph including start and end
Map<Position, List<Position>> graph = new HashMap<Position, List<Position>>();
@@ -11,7 +11,7 @@ import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.PolarDiagram;
import com.sap.sailing.simulator.SimulationParameters;
@@ -130,7 +130,7 @@ public class PathGeneratorDynProgForward extends PathGeneratorBase {
public Path getPath() {
// retrieve simulation parameters
Boundary boundary = new RectangularBoundary(this.parameters.getCourse().get(0), this.parameters
Grid boundary = new RectangularGrid(this.parameters.getCourse().get(0), this.parameters
.getCourse().get(1));// simulationParameters.getBoundaries();
WindFieldGenerator windField = this.parameters.getWindField();
PolarDiagram polarDiagram = this.parameters.getBoatPolarDiagram();
@@ -162,7 +162,7 @@ public class PathGeneratorDynProgForward extends PathGeneratorBase {
}
// generate grid positions using sgridh and sgridv
Position[][] sailGrid = boundary.extractGrid(spatialGridsizeHorizontal, spatialGridsizeVertical, 0, 0);
Position[][] sailGrid = boundary.generatePositions(spatialGridsizeHorizontal, spatialGridsizeVertical, 0, 0);
// optimization grid indexes:
// vertical steps: 0, ..., v-1
@@ -1,421 +0,0 @@
package com.sap.sailing.simulator.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.PolarDiagram;
import com.sap.sailing.simulator.SimulationParameters;
import com.sap.sailing.simulator.TimedPosition;
import com.sap.sailing.simulator.TimedPositionWithSpeed;
import com.sap.sailing.simulator.windfield.WindFieldGenerator;
public class PathGeneratorTreeGrowTarget extends PathGeneratorBase {
private static Logger logger = Logger.getLogger("com.sap.sailing");
int maxLeft;
int maxRight;
boolean startLeft;
public PathGeneratorTreeGrowTarget(SimulationParameters params) {
this.parameters = params;
}
public void setEvaluationParameters(int maxLeftVal, int maxRightVal, boolean startLeftVal) {
this.maxLeft = maxLeftVal;
this.maxRight = maxRightVal;
this.startLeft = startLeftVal;
}
class PathCand implements Comparable<PathCand> {
public PathCand(TimedPosition pos, double vrt, double hrz, String path) {
this.pos = pos;
this.vrt = vrt;
this.hrz = hrz;
this.path = path;
}
TimedPosition pos;
double vrt;
double hrz;
String path;
/*@Override
// sort ascending by horizontal distance
public int compareTo(PathCand other) {
if (Math.abs(this.hrz) == Math.abs(other.hrz))
return 0;
return (Math.abs(this.hrz) < Math.abs(other.hrz) ? -1 : +1);
}*/
@Override
// sort descending by height
public int compareTo(PathCand other) {
if (this.vrt == other.vrt) {
if (Math.abs(this.hrz) == Math.abs(other.hrz)) {
return 0;
} else {
return (Math.abs(this.hrz) < Math.abs(other.hrz) ? -1 : +1);
}
} else {
return (this.vrt > other.vrt ? -1 : +1);
}
}
}
// generate step
TimedPosition getStep(TimedPosition pos, long timeStep, long turnLoss, char prevDirection, char nextDirection) {
double offDeg = 5.0;
WindFieldGenerator wf = this.parameters.getWindField();
TimePoint curTime = pos.getTimePoint();
Position curPosition = pos.getPosition();
Wind posWind = wf.getWind(new TimedPositionWithSpeedImpl(curTime, curPosition, null));
PolarDiagram pd = this.parameters.getBoatPolarDiagram();
pd.setWind(posWind);
// get beat-angle left and right
Bearing travelBearing = null;
Bearing tmpBearing = null;
if (nextDirection == 'L') {
travelBearing = pd.optimalDirectionsUpwind()[0];
} else if (nextDirection == 'R') {
travelBearing = pd.optimalDirectionsUpwind()[1];
} else if (nextDirection == 'M') {
tmpBearing = pd.optimalDirectionsUpwind()[0];
travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg));
} else if (nextDirection == 'S') {
tmpBearing = pd.optimalDirectionsUpwind()[1];
travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg));
}
// determine beat-speed left and right
SpeedWithBearing travelSpeed = pd.getSpeedAtBearing(travelBearing);
TimePoint travelTime;
TimePoint nextTime = new MillisecondsTimePoint(curTime.asMillis()+timeStep);
char prevBaseDirection = prevDirection;
if (prevBaseDirection == 'M') {
prevBaseDirection = 'L';
}
if (prevBaseDirection == 'S') {
prevBaseDirection = 'R';
}
char nextBaseDirection = nextDirection;
if (nextBaseDirection == 'M') {
nextBaseDirection = 'L';
}
if (nextBaseDirection == 'S') {
nextBaseDirection = 'R';
}
boolean sameBaseDirection = (nextBaseDirection == prevBaseDirection)||(prevBaseDirection == '0');
if (sameBaseDirection) {
travelTime = nextTime;
} else {
travelTime = new MillisecondsTimePoint(nextTime.asMillis() - turnLoss);
}
return new TimedPositionImpl(nextTime, travelSpeed.travelTo(curPosition, curTime, travelTime));
}
PathCand getPathCand(PathCand path, char nextDirection, long timeStep, long turnLoss, Position posStart, Bearing bearVrt, double tgtHeight) {
// calculate next path position (taking turn-loss into account)
TimedPosition pathPos = this.getStep(path.pos, timeStep, turnLoss, path.path.charAt(path.path.length()-1), nextDirection);
// calculate height-position with reference to race course
Position posHeight = pathPos.getPosition().projectToLineThrough(posStart, bearVrt);
// calculate vertical distance as distance of height-position to start
double vrtDist = Math.round(posHeight.getDistance(posStart).getMeters()*100.0)/100.0;
/*if (vrtDist > tgtHeight) {
// scale last step so that vrtDist ~ tgtHeight
Position prevPos = path.pos.getPosition();
TimePoint prevTime = path.pos.getTimePoint();
double heightFrac = (tgtHeight - path.vrt) / (vrtDist - path.vrt);
Position newPos = prevPos.translateGreatCircle(prevPos.getBearingGreatCircle(pathPos.getPosition()), prevPos.getDistance(pathPos.getPosition()).scale(heightFrac));
TimePoint newTime = new MillisecondsTimePoint(Math.round(prevTime.asMillis() + (pathPos.getTimePoint().asMillis()-prevTime.asMillis())*heightFrac));
pathPos = new TimedPositionImpl(newTime, newPos);
posHeight = pathPos.getPosition().projectToLineThrough(posStart, bearVrt);
}*/
// calculate horizontal side: left or right in reference to race course
double posSide = 1;
double posBear = posStart.getBearingGreatCircle(pathPos.getPosition()).getDegrees();
if ((posBear < 0.0)||(posBear > 180.0)) {
posSide = -1;
} else if ((posBear == 0.0)||(posBear == 180.0)) {
posSide = 0;
}
// calculate horizontal distance as distance of height-position to current position
double hrzDist = Math.round(posSide*posHeight.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0;
//System.out.println(""+hrzDist+", "+vrtDist+", "+pathPos.getPosition().getLatDeg()+", "+pathPos.getPosition().getLngDeg()+", "+posHeight.getLatDeg()+", "+posHeight.getLngDeg());
// extend path-string by step-direction
String pathStr = path.path + nextDirection;
return (new PathCand(pathPos, vrtDist, hrzDist, pathStr));
}
// generate path candidates based on beat angles
List<PathCand> getPathCandsBeat(PathCand path, long timeStep, long turnLoss, Position posStart, Bearing bearVrt, double tgtHeight) {
List<PathCand> result = new ArrayList<PathCand>();
PathCand newPathCand;
// step left
newPathCand = getPathCand(path, 'L', timeStep, turnLoss, posStart, bearVrt, tgtHeight);
result.add(newPathCand);
// step wide left
newPathCand = getPathCand(path, 'M', timeStep, turnLoss, posStart, bearVrt, tgtHeight);
result.add(newPathCand);
// step right
newPathCand = getPathCand(path, 'R', timeStep, turnLoss, posStart, bearVrt, tgtHeight);
result.add(newPathCand);
// step wide right
newPathCand = getPathCand(path, 'S', timeStep, turnLoss, posStart, bearVrt, tgtHeight);
result.add(newPathCand);
return result;
}
@Override
public Path getPath() {
WindFieldGenerator wf = this.parameters.getWindField();
PolarDiagram pd = this.parameters.getBoatPolarDiagram();
Position startPos = this.parameters.getCourse().get(0);
Position endPos = this.parameters.getCourse().get(1);
TimePoint startTime = wf.getStartTime();// new MillisecondsTimePoint(0);
List<TimedPositionWithSpeed> path = new ArrayList<TimedPositionWithSpeed>();
Position currentPosition = startPos;
TimePoint currentTime = startTime;
Distance distStartEnd = startPos.getDistance(endPos);
double distStartEndMeters = distStartEnd.getMeters();
long timeStep = wf.getTimeStep().asMillis()/3;
logger.info("Time step :" + timeStep);
long turnLoss = pd.getTurnLoss(); // 4000; // time lost when doing a turn
Wind wndStart = wf.getWind(new TimedPositionWithSpeedImpl(startTime, startPos, null));
logger.fine("wndStart speed:" + wndStart.getKnots() + " angle:" + wndStart.getBearing().getDegrees());
pd.setWind(wndStart);
Bearing bearVrt = startPos.getBearingGreatCircle(endPos);
//Bearing bearHrz = bearVrt.add(new DegreeBearingImpl(90.0));
Position middlePos = startPos.translateGreatCircle(bearVrt, distStartEnd.scale(0.5));
System.out.println("start : "+startPos.getLatDeg()+", "+startPos.getLngDeg());
System.out.println("middle: "+middlePos.getLatDeg()+", "+middlePos.getLngDeg());
System.out.println("end : "+endPos.getLatDeg()+", "+endPos.getLngDeg());
// initialize list of paths
PathCand initPath = new PathCand(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, "0");
String initPathStr = "0";
//String initPathStr = "0RRRRRRRRRRRRRRRLLL";
//String initPathStr = "0RRRRRRRRRRRRRRRRRR";
//PathCand initPath = new PathCand(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, ); //"0RRRRRRRRRRRRRRR");
if (initPathStr.length()>1) {
//char prevDirection = '0';
char nextDirection = '0';
for(int idx=1; idx<initPathStr.length(); idx++) {
nextDirection = initPathStr.charAt(idx);
//TimedPosition newPosition = this.getStep(initPath.pos, timeStep, turnLoss, prevDirection, nextDirection);
//initPath.pos = newPosition;
PathCand newPathCand = getPathCand(initPath, nextDirection, timeStep, turnLoss, startPos, bearVrt, distStartEndMeters);
initPath = newPathCand;
//prevDirection = nextDirection;
}
}
List<PathCand> allPaths = new ArrayList<PathCand>();
List<PathCand> trgPaths = new ArrayList<PathCand>();
allPaths.add(initPath);
TimedPosition tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, '0', 'L');
double tstDist1 = startPos.getDistance(tstPosition.getPosition()).getMeters();
tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, '0', 'R');
double tstDist2 = startPos.getDistance(tstPosition.getPosition()).getMeters();
double hrzBinSize = (tstDist1 + tstDist2)/3; // horizontal bin size in meters
System.out.println("Horizontal Bin Size: "+hrzBinSize);
double oobFact = 0.75; // out-of-bounds factor
//int hrzLeft = (int)Math.round(Math.floor( -distStartEndMeters / 2.0 / hrzBinSize / oobFact ));
//int hrzRight = (int)Math.round(Math.floor( distStartEndMeters / 2.0 / hrzBinSize / oobFact ));
boolean reachedEnd = false;
int addSteps = 0;
int finalSteps = 0; // maximum number of additional steps after first target-path found
//for(int count=0; count<10; count++) {
while ((!reachedEnd)||(addSteps<finalSteps)) {
if (reachedEnd) {
addSteps++;
}
// generate new paths
double hrzMin = 0;
double hrzMax = 0;
List<PathCand> newPathCands;
List<PathCand> newPaths = new ArrayList<PathCand>();
for(PathCand curPath : allPaths) {
if ((curPath.vrt > distStartEndMeters)) {
continue;
} else {
newPathCands = this.getPathCandsBeat(curPath, timeStep, turnLoss, startPos, bearVrt, distStartEndMeters);
}
for(PathCand newPath : newPathCands) {
newPaths.add(newPath);
if (newPath.hrz < hrzMin) {
hrzMin = newPath.hrz;
}
if (newPath.hrz > hrzMax) {
hrzMax = newPath.hrz;
}
}
}
int hrzLeft = (int)Math.round(Math.floor( (hrzMin + hrzBinSize/2.0) / hrzBinSize ));
int hrzRight = (int)Math.round(Math.floor( (hrzMax + hrzBinSize/2.0) / hrzBinSize ));
//System.out.println("hrzLeft: "+hrzLeft+", hrzRight: "+hrzRight);
// build map of best path per hrz-bin
int countPath = 0;
int mapSize = hrzRight-hrzLeft+1;
//System.out.println("MapSize: "+mapSize);
int[] binIdx = new int[mapSize]; // TODO: init with zero?
double[] binVal = new double[mapSize]; // TODO: init with zero?
for(int curIdx=0; curIdx<newPaths.size(); curIdx++) {
PathCand curPath = newPaths.get(curIdx);
// check whether path is *outside* regatta-area
double distFromMiddleMeters = middlePos.getDistance(curPath.pos.getPosition()).getMeters();
if (distFromMiddleMeters > oobFact*distStartEndMeters) {
continue; // ignore curPath
}
// increase path-counter
countPath++;
// determine map-key
int curKey = (int)Math.round(Math.floor( (curPath.hrz + hrzBinSize/2.0) / hrzBinSize )) - hrzLeft;
/*String allR = "0" + (new String(new char[curPath.path.length()-1]).replace('\0', 'R'));
if (curPath.path.equals(allR)) {
System.out.println(""+curPath.path+": "+curPath.hrz+", "+curPath.vrt+", "+curPath.pos.getPosition().getLatDeg()+", "+curPath.pos.getPosition().getLngDeg());
}*/
// check whether curPath is better then the ones looked at
if ((curKey>=0)&&(curKey < mapSize)) {
if (curPath.vrt > binVal[curKey]) {
binVal[curKey] = curPath.vrt;
binIdx[curKey] = curIdx+1;
}
}
}
// check if there are still paths inside regatta-area
if (countPath > 0) {
// take best from each horizontal-bin and check if target is reached
allPaths = new ArrayList<PathCand>();
for (int curKey = 0; curKey < mapSize; curKey++) {
if (binIdx[curKey] > 0) {
PathCand curPath = newPaths.get(binIdx[curKey] - 1);
allPaths.add(curPath);
// debug output
/*if ((Math.abs(curKey + hrzLeft) <= 3)) {
System.out.println("" + curPath.path + ": " + (curKey + hrzLeft) + ", vrt:" + curPath.vrt + ", hrz:" + curPath.hrz + ", bin:" + (Math.floor((curPath.hrz + hrzBinSize / 2.0) / hrzBinSize)));
}*/
// terminate path-search if paths close enough to target are found
if ((curPath.vrt > distStartEndMeters)) {
if ((Math.abs(curKey + hrzLeft) <= 2)) {
reachedEnd = true;
trgPaths.add(curPath); // add path to list of target-paths
}
}
}
}
} else {
// terminate path-search as no path inside regatta-area are left
reachedEnd = true;
}
}
// if no target-paths were found, return empty path
if (trgPaths.size() == 0) {
//trgPaths = allPaths; // TODO: only for testing; remove lateron
return new PathImpl(path, wf); // return empty path
}
// sort target-paths ascending by distance-to-target
Collections.sort(trgPaths);
// debug output
for(PathCand curPath : trgPaths) {
System.out.print(""+curPath.path+": "+curPath.pos.getTimePoint().asMillis()+", "+curPath.pos.getPosition().getLatDeg()+", "+curPath.pos.getPosition().getLngDeg()+", ");
System.out.println(" height:"+curPath.vrt+" of "+startPos.getDistance(endPos).getMeters()+", dist:"+curPath.hrz+" ~ "+curPath.pos.getPosition().getDistance(endPos));
}
//
// fill gwt-path
//
// generate intermediate steps
PathCand bstPath = trgPaths.get(0); // target-path ending closest to target
TimedPositionWithSpeed curPosition = null;
char nextDirection = '0';
char prevDirection = '0';
for(int step=0; step<(bstPath.path.length()-1); step++) {
nextDirection = bstPath.path.charAt(step);
if (nextDirection == '0') {
curPosition = new TimedPositionWithSpeedImpl(startTime, startPos, null);
path.add(curPosition);
} else {
TimedPosition newPosition = this.getStep(curPosition, timeStep, turnLoss, prevDirection, nextDirection);
curPosition = new TimedPositionWithSpeedImpl(newPosition.getTimePoint(), newPosition.getPosition(), null);
path.add(curPosition);
}
prevDirection = nextDirection;
}
// add final position (rescaled before to end on height of target)
path.add(new TimedPositionWithSpeedImpl(bstPath.pos.getTimePoint(), bstPath.pos.getPosition(), null));
return new PathImpl(path, wf);
}
}
@@ -1,7 +1,11 @@
package com.sap.sailing.simulator.impl;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Logger;
@@ -27,57 +31,62 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
private static Logger logger = Logger.getLogger("com.sap.sailing");
private boolean debugMsgOn = false;
double oobFact = 0.75; // out-of-bounds factor
int maxTurns = 0;
boolean upwindLeg = false;
String initPathStr = "0";
PathCandidate bestCand = null;
long usedTimeStep = 0;
boolean gridStore = false;
ArrayList<List<PathCandidate>> gridPositions = null;
ArrayList<List<PathCandidate>> isocPositions = null;
String gridFile = null;
public PathGeneratorTreeGrowWind(SimulationParameters params) {
this.parameters = params;
}
public void setEvaluationParameters(String startDirection, int maxTurns) {
public void setEvaluationParameters(String startDirection, int maxTurns, String gridFile) {
if (startDirection != null) {
this.initPathStr = "0" + startDirection;
} else {
this.initPathStr = "0";
}
this.maxTurns = maxTurns;
this.gridFile = gridFile;
if (this.gridFile != null) {
this.gridStore = true;
this.gridPositions = new ArrayList<List<PathCandidate>>();
this.isocPositions = new ArrayList<List<PathCandidate>>();
} else {
this.gridStore = false;
this.gridPositions = null;
this.isocPositions = null;
}
}
class PathCandidate implements Comparable<PathCandidate> {
public PathCandidate(TimedPosition pos, double vrt, double hrz, int trn, String path) {
this.pos = pos;
this.vrt = vrt;
this.hrz = hrz;
this.trn = trn;
this.path = path;
class SortPathCandsAbsHorizontally implements Comparator<PathCandidate> {
@Override
public int compare(PathCandidate p1, PathCandidate p2) {
if (Math.abs(p1.hrz) == Math.abs(p2.hrz)) {
return 0;
} else {
return (Math.abs(p1.hrz) < Math.abs(p2.hrz) ? -1 : +1);
}
}
TimedPosition pos;
double vrt;
double hrz;
int trn;
String path;
}
class SortPathCandsHorizontally implements Comparator<PathCandidate> {
/*@Override
// sort ascending by horizontal distance
public int compareTo(PathCand other) {
if (Math.abs(this.hrz) == Math.abs(other.hrz))
return 0;
return (Math.abs(this.hrz) < Math.abs(other.hrz) ? -1 : +1);
}*/
@Override
// sort descending by height
public int compareTo(PathCandidate other) {
if (this.vrt == other.vrt) {
if (Math.abs(this.hrz) == Math.abs(other.hrz)) {
return 0;
} else {
return (Math.abs(this.hrz) < Math.abs(other.hrz) ? -1 : +1);
}
public int compare(PathCandidate p1, PathCandidate p2) {
if (p1.hrz == p2.hrz) {
return 0;
} else {
return (this.vrt > other.vrt ? -1 : +1);
}
return (p1.hrz < p2.hrz ? -1 : +1);
}
}
}
@@ -86,36 +95,57 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
PathCandidate getBestCand() {
return this.bestCand;
}
long getUsedTimeStep() {
return this.usedTimeStep;
}
// generate step in one of the possible directions
// default: L - left, R - right
// extended: M - wide left, S - wide right
Util.Pair<TimedPosition,Wind> getStep(TimedPosition pos, long timeStep, long turnLoss, boolean sameBaseDirection, char nextDirection) {
TimedPosition getStep(TimedPosition pos, long timeStep, long turnLoss, boolean sameBaseDirection, char nextDirection) {
double offDeg = 5.0;
double offDeg = 3.0;
WindFieldGenerator wf = this.parameters.getWindField();
TimePoint curTime = pos.getTimePoint();
Position curPosition = pos.getPosition();
Wind posWind = wf.getWind(new TimedPositionWithSpeedImpl(curTime, curPosition, null));
Wind posWind = wf.getWind(new TimedPositionImpl(curTime, curPosition));
PolarDiagram pd = this.parameters.getBoatPolarDiagram();
pd.setWind(posWind);
Wind appWind = new WindImpl(posWind.getPosition(), posWind.getTimePoint(), pd.getWind());;
// get beat-angle left and right
Bearing travelBearing = null;
Bearing tmpBearing = null;
if (nextDirection == 'L') {
travelBearing = pd.optimalDirectionsUpwind()[0];
if (this.upwindLeg) {
travelBearing = pd.optimalDirectionsUpwind()[0];
} else {
travelBearing = pd.optimalDirectionsDownwind()[0];
}
} else if (nextDirection == 'R') {
travelBearing = pd.optimalDirectionsUpwind()[1];
if (this.upwindLeg) {
travelBearing = pd.optimalDirectionsUpwind()[1];
} else {
travelBearing = pd.optimalDirectionsDownwind()[1];
}
} else if (nextDirection == 'M') {
tmpBearing = pd.optimalDirectionsUpwind()[0];
travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg));
if (this.upwindLeg) {
tmpBearing = pd.optimalDirectionsUpwind()[0];
travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg));
} else {
tmpBearing = pd.optimalDirectionsDownwind()[0];
travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg));
}
} else if (nextDirection == 'S') {
tmpBearing = pd.optimalDirectionsUpwind()[1];
travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg));
if (this.upwindLeg) {
tmpBearing = pd.optimalDirectionsUpwind()[1];
travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg));
} else {
tmpBearing = pd.optimalDirectionsDownwind()[1];
travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg));
}
}
// determine beat-speed left and right
@@ -129,7 +159,8 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
travelTime = new MillisecondsTimePoint(nextTime.asMillis() - turnLoss);
}
return new Util.Pair<TimedPosition,Wind>(new TimedPositionImpl(nextTime, travelSpeed.travelTo(curPosition, curTime, travelTime)), appWind);
Position nextPosition = travelSpeed.travelTo(curPosition, curTime, travelTime);
return new TimedPositionImpl(nextTime, nextPosition);
}
// use base direction to distinguish direction changes that do or don't require a turn
@@ -167,20 +198,29 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
}
// calculate next path position (taking turn-loss into account)
Util.Pair<TimedPosition,Wind> nextStep = this.getStep(path.pos, timeStep, turnLoss, sameBaseDirection, nextDirection);
TimedPosition pathPos = nextStep.getA();
Wind posWind = nextStep.getB();
TimedPosition pathPos = this.getStep(path.pos, timeStep, turnLoss, sameBaseDirection, nextDirection);
// determine apparent wind at next path position & time
Wind posWind = this.parameters.getWindField().getWind(pathPos);
PolarDiagram pd = this.parameters.getBoatPolarDiagram();
pd.setWind(posWind);
Wind appWind = new WindImpl(posWind.getPosition(), posWind.getTimePoint(), pd.getWind());
// calculate height-position with reference to race course
Position posHeight = pathPos.getPosition().projectToLineThrough(posEnd, posWind.getBearing());
Bearing bearVrt = posStart.getBearingGreatCircle(posEnd);
Position posHeightTrgt = pathPos.getPosition().projectToLineThrough(posStart, bearVrt);
//Position posHeightWind = pathPos.getPosition().projectToLineThrough(posRef, posWind.getBearing());
// calculate height-position with reference to apparent wind
Position posHeight = pathPos.getPosition().projectToLineThrough(posEnd, appWind.getBearing());
// calculate vertical distance as distance of height-position to start
//double vrtDist = tgtHeight - Math.round(posHeight.getDistance(posEnd).getMeters()*100.0)/100.0;
double vrtDist = Math.round(posHeight.getDistance(posStart).getMeters()*100.0)/100.0;
// calculate vertical distance as distance of height-position to end
Bearing bearHeight = posEnd.getBearingGreatCircle(posHeight);
double bearHeightSide = appWind.getBearing().getDifferenceTo(bearHeight).getDegrees();
double vrtSide = (this.upwindLeg ? -1.0 : +1.0);
if (Math.abs(bearHeightSide) > 170.0) {
vrtSide = (this.upwindLeg ? +1.0 : -1.0);
}
double vrtDist = vrtSide*Math.round(posHeight.getDistance(posEnd).getMeters()*1000.0)/1000.0;
//
// TODO: scale last step to exactly reach height of posEnd, and adjust time correspondingly
//
/*if (vrtDist > tgtHeight) {
// scale last step so that vrtDist ~ tgtHeight
Position prevPos = path.pos.getPosition();
@@ -194,23 +234,23 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
// calculate horizontal side: left or right in reference to race course
double posSide = 1;
//double posBear = posWind.getBearing().getDegrees() - posEnd.getBearingGreatCircle(pathPos.getPosition()).getDegrees();
double posBear = posStart.getBearingGreatCircle(pathPos.getPosition()).getDegrees();
if ((posBear < 0.0)||(posBear > 180.0)) {
Bearing posBear = posStart.getBearingGreatCircle(pathPos.getPosition());
Bearing bearVrt = posStart.getBearingGreatCircle(posEnd);
double posBearDiff = bearVrt.getDifferenceTo(posBear).getDegrees();
if ((posBearDiff < 0.0)||(posBearDiff > 180.0)) {
posSide = -1;
} else if ((posBear == 0.0)||(posBear == 180.0)) {
} else if ((posBearDiff == 0.0)||(posBearDiff == 180.0)) {
posSide = 0;
}
// calculate horizontal distance as distance of height-position to current position
//double hrzDist = Math.round(posSide*posHeight.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0;
double hrzDist = Math.round(posSide*posHeightTrgt.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0;
//System.out.println(""+hrzDist+", "+vrtDist+", "+pathPos.getPosition().getLatDeg()+", "+pathPos.getPosition().getLngDeg()+", "+posHeight.getLatDeg()+", "+posHeight.getLngDeg());
Position posHeightTrgt = pathPos.getPosition().projectToLineThrough(posStart, bearVrt);
double hrzDist = Math.round(posSide*posHeightTrgt.getDistance(pathPos.getPosition()).getMeters()*1000.0)/1000.0;
// extend path-string by step-direction
String pathStr = path.path + nextDirection;
char nextBaseDirection = this.getBaseDirection(nextDirection);
return (new PathCandidate(pathPos, vrtDist, hrzDist, turnCount, pathStr));
return (new PathCandidate(pathPos, vrtDist, hrzDist, turnCount, pathStr, nextBaseDirection, appWind));
}
@@ -256,13 +296,200 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
return result;
}
Util.Pair<List<PathCandidate>,List<PathCandidate>> generateCandidate(List<PathCandidate> oldPaths, long timeStep, long turnLoss, Position posStart, Position posMiddle, Position posEnd, double tgtHeight) {
List<PathCandidate> newPathCands;
List<PathCandidate> leftPaths = new ArrayList<PathCandidate>();
List<PathCandidate> rightPaths = new ArrayList<PathCandidate>();
for(PathCandidate curPath : oldPaths) {
newPathCands = this.getPathCandsBeatWind(curPath, timeStep, turnLoss, posStart, posEnd, tgtHeight);
for (PathCandidate curNewPath : newPathCands) {
// check whether path is *outside* regatta-area
double distFromMiddleMeters = posMiddle.getDistance(curPath.pos.getPosition()).getMeters();
if (distFromMiddleMeters > oobFact * tgtHeight) {
continue; // ignore curPath
}
if (curNewPath.sid == 'L') {
leftPaths.add(curNewPath);
} else if (curNewPath.sid == 'R') {
rightPaths.add(curNewPath);
}
}
}
Util.Pair<List<PathCandidate>,List<PathCandidate>> newPaths = new Util.Pair<List<PathCandidate>,List<PathCandidate>>(leftPaths, rightPaths);
return newPaths;
}
List<PathCandidate> filterCandidates(List<PathCandidate> allCands, double hrzBinWidth) {
boolean[] filterMap = new boolean[allCands.size()];
// sort candidates by horizontal distance
Comparator<PathCandidate> sortHorizontal = new SortPathCandsHorizontally();
Collections.sort(allCands, sortHorizontal);
// start scan with index 0
int idxL = 0;
int idxR = 0;
// for each candidate, check the neighborhoods and identify bad candidates
for(int idx = 0; idx < allCands.size(); idx++) {
// current horizontal distance
double hrzDist = allCands.get(idx).hrz;
// align left index
while(Math.abs(hrzDist - allCands.get(idxL).hrz) > hrzBinWidth) {
idxL++;
}
// align right index
boolean finished = false;
while(!finished && (idxR < (allCands.size()-1))) {
if (Math.abs(hrzDist - allCands.get(idxR+1).hrz) <= hrzBinWidth) {
idxR++;
} else {
finished = true;
}
}
// search maximum height
// in neighborhood idxL, ..., idxR
// init max for search
int vrtIdx = idxL;
double vrtMax = allCands.get(vrtIdx).vrt;
filterMap[vrtIdx] = false;
// evaluate remainder of neighborhood
if (idxL < idxR) {
for(int jdx = (idxL+1); jdx <= idxR; jdx++) {
if (allCands.get(jdx).vrt > vrtMax) {
// reset previous max candidate
filterMap[vrtIdx] = true;
// keep max height
vrtMax = allCands.get(jdx).vrt;
// keep max index
vrtIdx = jdx;
// set current max candidate
filterMap[vrtIdx] = false;
} else {
filterMap[jdx] = true;
}
}
}
} // endfor each candidate
// collect all good candidates (i.e. filterMap == false)
List<PathCandidate> filterCands = new ArrayList<PathCandidate>();
for(int idx=0; idx < allCands.size(); idx++) {
if (!filterMap[idx]) {
filterCands.add(allCands.get(idx));
}
}
// return remaining good candidates
return filterCands;
}
List<PathCandidate> filterIsochrone(List<PathCandidate> allCands, double hrzBinWidth) {
boolean[] filterMap = new boolean[allCands.size()];
for(int idx = 0; idx < allCands.size(); idx++) {
filterMap[idx] = true;
}
// sort candidates by horizontal distance
Comparator<PathCandidate> sortHorizontal = new SortPathCandsHorizontally();
Collections.sort(allCands, sortHorizontal);
// start scan with index 0
int idxL = 0;
int idxR = 0;
// for each candidate, check the neighborhoods and identify bad candidates
for(int idx = 0; idx < allCands.size(); idx++) {
// current horizontal distance
double hrzDist = allCands.get(idx).hrz;
// align left index
while(Math.abs(hrzDist - allCands.get(idxL).hrz) > hrzBinWidth) {
idxL++;
}
// align right index
boolean finished = false;
while(!finished && (idxR < (allCands.size()-1))) {
if (Math.abs(hrzDist - allCands.get(idxR+1).hrz) <= hrzBinWidth) {
idxR++;
} else {
finished = true;
}
}
// search maximum height
// in neighborhood idxL, ..., idxR
// init max for search
ArrayList<Integer> vrtIdx = new ArrayList<Integer>();
vrtIdx.add(idxL);
double vrtMax = allCands.get(idxL).vrt;
// evaluate remainder of neighborhood
if (idxL < idxR) {
for(int jdx = (idxL+1); jdx <= idxR; jdx++) {
if (allCands.get(jdx).vrt > vrtMax) {
// keep max height
vrtMax = allCands.get(jdx).vrt;
// keep max index
vrtIdx = new ArrayList<Integer>();
vrtIdx.add(jdx);
} else if (allCands.get(jdx).vrt == vrtMax) {
// add further max indexes
vrtIdx.add(jdx);
}
}
}
for(Integer jdx : vrtIdx) {
filterMap[jdx] = false;
}
} // endfor each candidate
// collect all good candidates (i.e. filterMap == false)
List<PathCandidate> filterCands = new ArrayList<PathCandidate>();
for(int idx=0; idx < allCands.size(); idx++) {
if (!filterMap[idx]) {
filterCands.add(allCands.get(idx));
}
}
// return remaining good candidates
return filterCands;
}
@Override
public Path getPath() {
WindFieldGenerator wf = this.parameters.getWindField();
PolarDiagram pd = this.parameters.getBoatPolarDiagram();
Position startPos = this.parameters.getCourse().get(0);
Position endPos = this.parameters.getCourse().get(1);
// test downwind: exchange start and end
//Position startPos = this.parameters.getCourse().get(1);
//Position endPos = this.parameters.getCourse().get(0);
TimePoint startTime = wf.getStartTime();// new MillisecondsTimePoint(0);
List<TimedPositionWithSpeed> path = new ArrayList<TimedPositionWithSpeed>();
@@ -272,25 +499,42 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
Distance distStartEnd = startPos.getDistance(endPos);
double distStartEndMeters = distStartEnd.getMeters();
long timeStep = wf.getTimeStep().asMillis()/3;
logger.info("Time step :" + timeStep);
long turnLoss = pd.getTurnLoss(); // 4000; // time lost when doing a turn
Wind wndStart = wf.getWind(new TimedPositionWithSpeedImpl(startTime, startPos, null));
logger.fine("wndStart speed:" + wndStart.getKnots() + " angle:" + wndStart.getBearing().getDegrees());
pd.setWind(wndStart);
Bearing bearVrt = startPos.getBearingGreatCircle(endPos);
//Bearing bearHrz = bearVrt.add(new DegreeBearingImpl(90.0));
Position middlePos = startPos.translateGreatCircle(bearVrt, distStartEnd.scale(0.5));
Bearing bearRCWind = wndStart.getBearing().getDifferenceTo(bearVrt);
String legType = "downwind";
this.upwindLeg = false;
if ((Math.abs(bearRCWind.getDegrees()) > 90.0)&&(Math.abs(bearRCWind.getDegrees()) < 270.0)) {
legType = "upwind";
this.upwindLeg = true;
}
if (debugMsgOn) {
System.out.println("start : "+startPos.getLatDeg()+", "+startPos.getLngDeg());
System.out.println("middle: "+middlePos.getLatDeg()+", "+middlePos.getLngDeg());
System.out.println("end : "+endPos.getLatDeg()+", "+endPos.getLngDeg());
}
logger.info("Leg Direction: "+legType);
long timeStep = wf.getTimeStep().asMillis()/2;
if (!this.upwindLeg) {
timeStep = timeStep / 2;
}
this.usedTimeStep = timeStep;
logger.info("Time step :" + timeStep);
long turnLoss = pd.getTurnLoss(); // 4000; // time lost when doing a turn
if (!this.upwindLeg) {
turnLoss = turnLoss / 2;
}
// calculate initial position according to initPathStr
PathCandidate initPath = new PathCandidate(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, 0, "0");
PathCandidate initPath = new PathCandidate(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, 0, "0", '0', wndStart);
if (initPathStr.length()>1) {
char nextDirection = '0';
for(int idx=1; idx<initPathStr.length(); idx++) {
@@ -304,17 +548,17 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
allPaths.add(initPath);
TimedPosition tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'L').getA();
TimedPosition tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'L');
double tstDist1 = startPos.getDistance(tstPosition.getPosition()).getMeters();
tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'R').getA();
tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'R');
double tstDist2 = startPos.getDistance(tstPosition.getPosition()).getMeters();
double hrzBinSize = (tstDist1 + tstDist2)/4; // horizontal bin size in meters
double hrzBinSize = (tstDist1 + tstDist2)/6.0; // horizontal bin size in meters
if (debugMsgOn) {
System.out.println("Horizontal Bin Size: "+hrzBinSize);
}
double oobFact = 0.75; // out-of-bounds factor
//double oobFact = 0.75; // out-of-bounds factor
boolean reachedEnd = false;
int addSteps = 0;
int finalSteps = 0; // maximum number of additional steps after first target-path found
@@ -325,109 +569,123 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
addSteps++;
}
// generate new paths
double hrzMin = 0;
double hrzMax = 0;
List<PathCandidate> newPathCands;
List<PathCandidate> newPaths = new ArrayList<PathCandidate>();
for(PathCandidate curPath : allPaths) {
// generate new candidates (inside regatta-area)
Util.Pair<List<PathCandidate>,List<PathCandidate>> newPaths = this.generateCandidate(allPaths, timeStep, turnLoss, startPos, middlePos, endPos, distStartEndMeters);
if ((curPath.vrt > distStartEndMeters)) {
continue;
} else {
//newPathCands = this.getPathCandsBeat(curPath, timeStep, turnLoss, startPos, bearVrt, distStartEndMeters);
newPathCands = this.getPathCandsBeatWind(curPath, timeStep, turnLoss, startPos, endPos, distStartEndMeters);
// select good candidates
List<PathCandidate> leftPaths = this.filterCandidates(newPaths.getA(), hrzBinSize/2.0);
List<PathCandidate> rightPaths = this.filterCandidates(newPaths.getB(), hrzBinSize/2.0);
List<PathCandidate> nextPaths = new ArrayList<PathCandidate> ();
nextPaths.addAll(leftPaths);
nextPaths.addAll(rightPaths);
allPaths = nextPaths;
if (this.gridStore) {
/*ArrayList<TimedPosition> isoChrone = new ArrayList<TimedPosition>();
for(PathCand curCand : allPaths) {
isoChrone.add(curCand.pos);
}
this.gridPositions.add(isoChrone);*/
for(PathCandidate newPath : newPathCands) {
this.gridPositions.add(allPaths);
List<PathCandidate> isocPaths = this.filterIsochrone(allPaths, hrzBinSize);
this.isocPositions.add(isocPaths);
newPaths.add(newPath);
if (newPath.hrz < hrzMin) {
hrzMin = newPath.hrz;
}
if (newPath.hrz > hrzMax) {
hrzMax = newPath.hrz;
}
}
}
int hrzLeft = (int)Math.round(Math.floor( (hrzMin + hrzBinSize/2.0) / hrzBinSize ));
int hrzRight = (int)Math.round(Math.floor( (hrzMax + hrzBinSize/2.0) / hrzBinSize ));
//System.out.println("hrzLeft: "+hrzLeft+", hrzRight: "+hrzRight);
// check if there are still paths in the regatta-area
if (allPaths.size() > 0) {
// build map of best path per hrz-bin
int countPath = 0;
int mapSize = hrzRight-hrzLeft+1;
//System.out.println("MapSize: "+mapSize);
int[] binIdx = new int[mapSize]; // TODO: init with zero?
double[] binVal = new double[mapSize];
// TODO: init of binVal
/*for(int idx = 0; idx < mapSize; idx++) {
binVal[idx] = 2*distStartEndMeters;
}*/
for(int curIdx=0; curIdx<newPaths.size(); curIdx++) {
PathCandidate curPath = newPaths.get(curIdx);
//if (curPath.vrt > 3900) {
if (debugMsgOn) {
System.out.println(""+curPath.path+": "+curPath.hrz+", "+curPath.vrt+", "+curPath.pos.getPosition().getLatDeg()+", "+curPath.pos.getPosition().getLngDeg());
}
//}
// check whether path is *outside* regatta-area
double distFromMiddleMeters = middlePos.getDistance(curPath.pos.getPosition()).getMeters();
if (distFromMiddleMeters > oobFact*distStartEndMeters) {
continue; // ignore curPath
}
// increase path-counter
countPath++;
// determine map-key
int curKey = (int)Math.round(Math.floor( (curPath.hrz + hrzBinSize/2.0) / hrzBinSize )) - hrzLeft;
/*String allR = "0" + (new String(new char[curPath.path.length()-1]).replace('\0', 'R'));
if (curPath.path.equals(allR)) {
System.out.println(""+curPath.path+": "+curPath.hrz+", "+curPath.vrt+", "+curPath.pos.getPosition().getLatDeg()+", "+curPath.pos.getPosition().getLngDeg());
}*/
// check whether curPath is better then the ones looked at
if ((curKey>=0)&&(curKey < mapSize)) {
if (curPath.vrt > binVal[curKey]) {
binVal[curKey] = curPath.vrt;
binIdx[curKey] = curIdx+1;
}
}
}
// check if there are still paths inside regatta-area
if (countPath > 0) {
// take best from each horizontal-bin and check if target is reached
allPaths = new ArrayList<PathCandidate>();
for (int curKey = 0; curKey < mapSize; curKey++) {
if (binIdx[curKey] > 0) {
PathCandidate curPath = newPaths.get(binIdx[curKey] - 1);
allPaths.add(curPath);
// debug output
/*if ((Math.abs(curKey + hrzLeft) <= 10)) {
System.out.println("" + curPath.path + ": " + (curKey + hrzLeft) + ", vrt:" + curPath.vrt + ", hrz:" + curPath.hrz + ", bin:" + (Math.floor((curPath.hrz + hrzBinSize / 2.0) / hrzBinSize)));
}*/
// terminate path-search if paths close enough to target are found
if ((curPath.vrt > distStartEndMeters)) {
if ((Math.abs(curKey + hrzLeft) <= 3)) {
reachedEnd = true;
trgPaths.add(curPath); // add path to list of target-paths
}
for(PathCandidate curPath : allPaths) {
// terminate path-search if paths are found that are close enough to target
//if ((curPath.vrt > distStartEndMeters)) {
if ((curPath.vrt > 0.0)) {
//logger.info("\ntPath: " + curPath.path + "\n Time: " + (Math.round((curPath.pos.getTimePoint().asMillis()-startTime.asMillis())/1000.0/60.0*10.0)/10.0)+", Height: "+curPath.vrt+" of "+(Math.round(startPos.getDistance(endPos).getMeters()*100.0)/100.0)+", Dist: "+curPath.hrz+"m ~ "+(Math.round(curPath.pos.getPosition().getDistance(endPos).getMeters()*100.0)/100.0)+"m");
int curBin = (int)Math.round(Math.floor( (curPath.hrz + hrzBinSize/2.0) / hrzBinSize ));
if ((Math.abs(curBin) <= 2)) {
reachedEnd = true;
trgPaths.add(curPath); // add path to list of target-paths
}
}
}
} else {
// terminate path-search as no path inside regatta-area are left
reachedEnd = true;
}
}
if (this.gridStore) {
double distResolution = distStartEndMeters*0.01;
BufferedWriter outputCSV;
try {
outputCSV = new BufferedWriter(new FileWriter(this.gridFile+"-grid.csv"));
outputCSV.write("step; lat; lng; time; side; path; vrt\n");
outputCSV.write("0; "+startPos.getLatDeg()+"; "+startPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; "+(-distStartEndMeters)+"\n");
outputCSV.write("0; "+endPos.getLatDeg()+"; "+endPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; 0\n");
int stepCount = 0;
for(List<PathCandidate> isoChrone : this.gridPositions) {
stepCount++;
PathCandidate prevPos = null;
for(PathCandidate isoPos : isoChrone) {
if (prevPos != null) {
if (prevPos.pos.getPosition().getDistance(isoPos.pos.getPosition()).getMeters() < distResolution) {
continue;
}
}
String outStr = ""+stepCount+"; "+isoPos.pos.getPosition().getLatDeg()+"; "+isoPos.pos.getPosition().getLngDeg()+"; "+(isoPos.pos.getTimePoint().asMillis()/1000)+"; "+isoPos.sid;
outStr += "; "+isoPos.path+"; "+isoPos.vrt;
outStr += "\n";
outputCSV.write(outStr);
prevPos = isoPos;
}
}
outputCSV.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
outputCSV = new BufferedWriter(new FileWriter(this.gridFile+"-isoc.csv"));
outputCSV.write("step; lat; lng; time; side; path; vrt\n");
outputCSV.write("0; "+startPos.getLatDeg()+"; "+startPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; "+(-distStartEndMeters)+"\n");
outputCSV.write("0; "+endPos.getLatDeg()+"; "+endPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; 0\n");
int stepCount = 0;
for(List<PathCandidate> isoChrone : this.isocPositions) {
stepCount++;
PathCandidate prevPos = null;
for(PathCandidate isoPos : isoChrone) {
if (prevPos != null) {
if (prevPos.pos.getPosition().getDistance(isoPos.pos.getPosition()).getMeters() < distResolution) {
continue;
}
}
String outStr = ""+stepCount+"; "+isoPos.pos.getPosition().getLatDeg()+"; "+isoPos.pos.getPosition().getLngDeg()+"; "+(isoPos.pos.getTimePoint().asMillis()/1000)+"; "+isoPos.sid;
outStr += "; "+isoPos.path+"; "+isoPos.vrt;
outStr += "\n";
outputCSV.write(outStr);
prevPos = isoPos;
}
}
outputCSV.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@@ -443,13 +701,11 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
Collections.sort(trgPaths);
// debug output
//if (debugMsgOn) {
for(PathCandidate curPath : trgPaths) {
logger.info("\nPath: " + curPath.path + "\n Time: " + (Math.round((curPath.pos.getTimePoint().asMillis()-startTime.asMillis())/1000.0/60.0*10.0)/10.0)+", Height: "+curPath.vrt+" of "+(Math.round(startPos.getDistance(endPos).getMeters()*100.0)/100.0)+", Dist: "+curPath.hrz+"m ~ "+(Math.round(curPath.pos.getPosition().getDistance(endPos).getMeters()*100.0)/100.0)+"m");
//System.out.print(""+curPath.path+": "+curPath.pos.getTimePoint().asMillis()+", "+curPath.pos.getPosition().getLatDeg()+", "+curPath.pos.getPosition().getLngDeg()+", ");
//System.out.println(" height:"+curPath.vrt+" of "+startPos.getDistance(endPos).getMeters()+", dist:"+curPath.hrz+" ~ "+curPath.pos.getPosition().getDistance(endPos));
}
//}
//
// fill gwt-path
@@ -472,7 +728,7 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
} else {
boolean sameBaseDirection = this.isSameDirection(prevDirection, nextDirection);
TimedPosition newPosition = this.getStep(curPosition, timeStep, turnLoss, sameBaseDirection, nextDirection).getA();
TimedPosition newPosition = this.getStep(curPosition, timeStep, turnLoss, sameBaseDirection, nextDirection);
curPosition = new TimedPositionWithSpeedImpl(newPosition.getTimePoint(), newPosition.getPosition(), null);
path.add(curPosition);
@@ -1,566 +0,0 @@
package com.sap.sailing.simulator.impl;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Logger;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.PolarDiagram;
import com.sap.sailing.simulator.SimulationParameters;
import com.sap.sailing.simulator.TimedPosition;
import com.sap.sailing.simulator.TimedPositionWithSpeed;
import com.sap.sailing.simulator.windfield.WindFieldGenerator;
import com.sap.sse.common.Util;
public class PathGeneratorTreeGrowWind2 extends PathGeneratorBase {
private static Logger logger = Logger.getLogger("com.sap.sailing");
private boolean debugMsgOn = false;
double oobFact = 0.75; // out-of-bounds factor
int maxTurns = 0;
String initPathStr = "0";
PathCandidate bestCand = null;
boolean gridStore = false;
ArrayList<List<PathCandidate>> gridPositions = null;
String gridFile = null;
public PathGeneratorTreeGrowWind2(SimulationParameters params) {
this.parameters = params;
}
public void setEvaluationParameters(String startDirection, int maxTurns, String gridFile) {
if (startDirection != null) {
this.initPathStr = "0" + startDirection;
} else {
this.initPathStr = "0";
}
this.maxTurns = maxTurns;
this.gridFile = gridFile;
if (this.gridFile != null) {
this.gridStore = true;
this.gridPositions = new ArrayList<List<PathCandidate>>();
} else {
this.gridStore = false;
this.gridPositions = null;
}
}
class SortPathCandsAbsHorizontally implements Comparator<PathCandidate> {
@Override
public int compare(PathCandidate p1, PathCandidate p2) {
if (Math.abs(p1.hrz) == Math.abs(p2.hrz)) {
return 0;
} else {
return (Math.abs(p1.hrz) < Math.abs(p2.hrz) ? -1 : +1);
}
}
}
class SortPathCandsHorizontally implements Comparator<PathCandidate> {
@Override
public int compare(PathCandidate p1, PathCandidate p2) {
if (p1.hrz == p2.hrz) {
return 0;
} else {
return (p1.hrz < p2.hrz ? -1 : +1);
}
}
}
// getter for evaluating best path cand propoerties further
PathCandidate getBestCand() {
return this.bestCand;
}
// generate step in one of the possible directions
// default: L - left, R - right
// extended: M - wide left, S - wide right
Util.Pair<TimedPosition,Wind> getStep(TimedPosition pos, long timeStep, long turnLoss, boolean sameBaseDirection, char nextDirection) {
double offDeg = 3.0;
WindFieldGenerator wf = this.parameters.getWindField();
TimePoint curTime = pos.getTimePoint();
Position curPosition = pos.getPosition();
Wind posWind = wf.getWind(new TimedPositionWithSpeedImpl(curTime, curPosition, null));
PolarDiagram pd = this.parameters.getBoatPolarDiagram();
pd.setWind(posWind);
Wind appWind = new WindImpl(posWind.getPosition(), posWind.getTimePoint(), pd.getWind());;
// get beat-angle left and right
Bearing travelBearing = null;
Bearing tmpBearing = null;
if (nextDirection == 'L') {
travelBearing = pd.optimalDirectionsUpwind()[0];
} else if (nextDirection == 'R') {
travelBearing = pd.optimalDirectionsUpwind()[1];
} else if (nextDirection == 'M') {
tmpBearing = pd.optimalDirectionsUpwind()[0];
travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg));
} else if (nextDirection == 'S') {
tmpBearing = pd.optimalDirectionsUpwind()[1];
travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg));
}
// determine beat-speed left and right
SpeedWithBearing travelSpeed = pd.getSpeedAtBearing(travelBearing);
TimePoint travelTime;
TimePoint nextTime = new MillisecondsTimePoint(curTime.asMillis()+timeStep);
if (sameBaseDirection) {
travelTime = nextTime;
} else {
travelTime = new MillisecondsTimePoint(nextTime.asMillis() - turnLoss);
}
return new Util.Pair<TimedPosition,Wind>(new TimedPositionImpl(nextTime, travelSpeed.travelTo(curPosition, curTime, travelTime)), appWind);
}
// use base direction to distinguish direction changes that do or don't require a turn
char getBaseDirection(char direction) {
char baseDirection = direction;
if (direction == 'M') {
baseDirection = 'L';
}
if (direction == 'S') {
baseDirection = 'R';
}
return baseDirection;
}
// check whether nextDirection is same base direction as previous direction, i.e. no turn
boolean isSameDirection(char prevDirection, char nextDirection) {
char prevBaseDirection = this.getBaseDirection(prevDirection);
char nextBaseDirection = this.getBaseDirection(nextDirection);
return ((nextBaseDirection == prevBaseDirection)||(prevBaseDirection == '0'));
}
// get path candidate measuring height towards (local, current-apparent) wind
PathCandidate getPathCandWind(PathCandidate path, char nextDirection, long timeStep, long turnLoss, Position posStart, Position posEnd, double tgtHeight) {
char prevDirection = path.path.charAt(path.path.length()-1);
boolean sameBaseDirection = this.isSameDirection(prevDirection, nextDirection);
int turnCount = path.trn;
if (!sameBaseDirection) {
turnCount++;
}
// calculate next path position (taking turn-loss into account)
Util.Pair<TimedPosition,Wind> nextStep = this.getStep(path.pos, timeStep, turnLoss, sameBaseDirection, nextDirection);
TimedPosition pathPos = nextStep.getA();
Wind posWind = nextStep.getB();
// calculate height-position with reference to race course
Position posHeight = pathPos.getPosition().projectToLineThrough(posEnd, posWind.getBearing());
Bearing bearVrt = posStart.getBearingGreatCircle(posEnd);
Position posHeightTrgt = pathPos.getPosition().projectToLineThrough(posStart, bearVrt);
//Position posHeightWind = pathPos.getPosition().projectToLineThrough(posRef, posWind.getBearing());
// calculate vertical distance as distance of height-position to start
//double vrtDist = Math.round(posHeightTrgt.getDistance(posStart).getMeters()*100.0)/100.0;
double vrtDist = Math.round(posHeight.getDistance(posStart).getMeters()*100.0)/100.0;
/*if (vrtDist > tgtHeight) {
// scale last step so that vrtDist ~ tgtHeight
Position prevPos = path.pos.getPosition();
TimePoint prevTime = path.pos.getTimePoint();
double heightFrac = (tgtHeight - path.vrt) / (vrtDist - path.vrt);
Position newPos = prevPos.translateGreatCircle(prevPos.getBearingGreatCircle(pathPos.getPosition()), prevPos.getDistance(pathPos.getPosition()).scale(heightFrac));
TimePoint newTime = new MillisecondsTimePoint(Math.round(prevTime.asMillis() + (pathPos.getTimePoint().asMillis()-prevTime.asMillis())*heightFrac));
pathPos = new TimedPositionImpl(newTime, newPos);
posHeight = pathPos.getPosition().projectToLineThrough(posStart, bearVrt);
}*/
// calculate horizontal side: left or right in reference to race course
double posSide = 1;
//double posBear = posWind.getBearing().getDegrees() - posEnd.getBearingGreatCircle(pathPos.getPosition()).getDegrees();
Bearing posBear = posStart.getBearingGreatCircle(pathPos.getPosition());
double posBearDiff = bearVrt.getDifferenceTo(posBear).getDegrees();
if ((posBearDiff < 0.0)||(posBearDiff > 180.0)) {
posSide = -1;
} else if ((posBearDiff == 0.0)||(posBearDiff == 180.0)) {
posSide = 0;
}
// calculate horizontal distance as distance of height-position to current position
//double hrzDist = Math.round(posSide*posHeight.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0;
double hrzDist = Math.round(posSide*posHeightTrgt.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0;
//System.out.println(""+hrzDist+", "+vrtDist+", "+pathPos.getPosition().getLatDeg()+", "+pathPos.getPosition().getLngDeg()+", "+posHeight.getLatDeg()+", "+posHeight.getLngDeg());
// extend path-string by step-direction
String pathStr = path.path + nextDirection;
char nextBaseDirection = this.getBaseDirection(nextDirection);
return (new PathCandidate(pathPos, vrtDist, hrzDist, turnCount, pathStr, nextBaseDirection, posWind));
}
// generate path candidates based on beat angles
List<PathCandidate> getPathCandsBeatWind(PathCandidate path, long timeStep, long turnLoss, Position posStart, Position posEnd, double tgtHeight) {
List<PathCandidate> result = new ArrayList<PathCandidate>();
PathCandidate newPathCand;
if (this.maxTurns > 0) {
char prevDirection = path.path.charAt(path.path.length()-1);
if ((path.trn < this.maxTurns)||(this.isSameDirection(prevDirection, 'L'))) {
newPathCand = getPathCandWind(path, 'L', timeStep, turnLoss, posStart, posEnd, tgtHeight);
result.add(newPathCand);
}
if ((path.trn < this.maxTurns)||(this.isSameDirection(prevDirection, 'R'))) {
newPathCand = getPathCandWind(path, 'R', timeStep, turnLoss, posStart, posEnd, tgtHeight);
result.add(newPathCand);
}
} else {
// step left
newPathCand = getPathCandWind(path, 'L', timeStep, turnLoss, posStart, posEnd, tgtHeight);
result.add(newPathCand);
// step wide left
//newPathCand = getPathCandWind(path, 'M', timeStep, turnLoss, posStart, posEnd, tgtHeight);
//result.add(newPathCand);
// step right
newPathCand = getPathCandWind(path, 'R', timeStep, turnLoss, posStart, posEnd, tgtHeight);
result.add(newPathCand);
// step wide right
//newPathCand = getPathCandWind(path, 'S', timeStep, turnLoss, posStart, posEnd, tgtHeight);
//result.add(newPathCand);
}
return result;
}
Util.Pair<List<PathCandidate>,List<PathCandidate>> generateCandidate(List<PathCandidate> oldPaths, long timeStep, long turnLoss, Position posStart, Position posMiddle, Position posEnd, double tgtHeight) {
List<PathCandidate> newPathCands;
List<PathCandidate> leftPaths = new ArrayList<PathCandidate>();
List<PathCandidate> rightPaths = new ArrayList<PathCandidate>();
for(PathCandidate curPath : oldPaths) {
newPathCands = this.getPathCandsBeatWind(curPath, timeStep, turnLoss, posStart, posEnd, tgtHeight);
for (PathCandidate curNewPath : newPathCands) {
// check whether path is *outside* regatta-area
double distFromMiddleMeters = posMiddle.getDistance(curPath.pos.getPosition()).getMeters();
if (distFromMiddleMeters > oobFact * tgtHeight) {
continue; // ignore curPath
}
if (curNewPath.sid == 'L') {
leftPaths.add(curNewPath);
} else if (curNewPath.sid == 'R') {
rightPaths.add(curNewPath);
}
}
}
Util.Pair<List<PathCandidate>,List<PathCandidate>> newPaths = new Util.Pair<List<PathCandidate>,List<PathCandidate>>(leftPaths, rightPaths);
return newPaths;
}
List<PathCandidate> filterCandidates(List<PathCandidate> allCands, double hrzBinWidth) {
boolean[] filterMap = new boolean[allCands.size()];
// sort candidates by horizontal distance
Comparator<PathCandidate> sortHorizontal = new SortPathCandsHorizontally();
Collections.sort(allCands, sortHorizontal);
// start scan with index 0
int idxL = 0;
int idxR = 0;
// for each candidate, check the neighborhoos and identify bad candidates
for(int idx = 0; idx < allCands.size(); idx++) {
// current horizontal distance
double hrzDist = allCands.get(idx).hrz;
// align left index
while(Math.abs(hrzDist - allCands.get(idxL).hrz) > hrzBinWidth) {
idxL++;
}
// align right index
boolean finished = false;
while(!finished && (idxR < (allCands.size()-1))) {
if (Math.abs(hrzDist - allCands.get(idxR+1).hrz) <= hrzBinWidth) {
idxR++;
} else {
finished = true;
}
}
// search maximum height
// in neighborhood idxL, ..., idxR
// init max for search
int vrtIdx = idxL;
double vrtMax = allCands.get(vrtIdx).vrt;
filterMap[vrtIdx] = false;
// evaluate remainder of neighborhood
if (idxL < idxR) {
for(int jdx = (idxL+1); jdx <= idxR; jdx++) {
if (allCands.get(jdx).vrt > vrtMax) {
// reset previous max candidate
filterMap[vrtIdx] = true;
// keep max height
vrtMax = allCands.get(jdx).vrt;
// keep max index
vrtIdx = jdx;
// set current max candidate
filterMap[vrtIdx] = false;
} else {
filterMap[jdx] = true;
}
}
}
} // endfor each candidate
// collect all good candidates (i.e. filterMap == false)
List<PathCandidate> filterCands = new ArrayList<PathCandidate>();
for(int idx=0; idx < allCands.size(); idx++) {
if (!filterMap[idx]) {
filterCands.add(allCands.get(idx));
}
}
// return remaining good candidates
return filterCands;
}
@Override
public Path getPath() {
WindFieldGenerator wf = this.parameters.getWindField();
PolarDiagram pd = this.parameters.getBoatPolarDiagram();
Position startPos = this.parameters.getCourse().get(0);
Position endPos = this.parameters.getCourse().get(1);
TimePoint startTime = wf.getStartTime();// new MillisecondsTimePoint(0);
List<TimedPositionWithSpeed> path = new ArrayList<TimedPositionWithSpeed>();
Position currentPosition = startPos;
TimePoint currentTime = startTime;
Distance distStartEnd = startPos.getDistance(endPos);
double distStartEndMeters = distStartEnd.getMeters();
long timeStep = wf.getTimeStep().asMillis()/3;
logger.info("Time step :" + timeStep);
long turnLoss = pd.getTurnLoss(); // 4000; // time lost when doing a turn
Wind wndStart = wf.getWind(new TimedPositionWithSpeedImpl(startTime, startPos, null));
logger.fine("wndStart speed:" + wndStart.getKnots() + " angle:" + wndStart.getBearing().getDegrees());
pd.setWind(wndStart);
Bearing bearVrt = startPos.getBearingGreatCircle(endPos);
//Bearing bearHrz = bearVrt.add(new DegreeBearingImpl(90.0));
Position middlePos = startPos.translateGreatCircle(bearVrt, distStartEnd.scale(0.5));
if (debugMsgOn) {
System.out.println("start : "+startPos.getLatDeg()+", "+startPos.getLngDeg());
System.out.println("middle: "+middlePos.getLatDeg()+", "+middlePos.getLngDeg());
System.out.println("end : "+endPos.getLatDeg()+", "+endPos.getLngDeg());
}
// calculate initial position according to initPathStr
PathCandidate initPath = new PathCandidate(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, 0, "0", '0', wndStart);
if (initPathStr.length()>1) {
char nextDirection = '0';
for(int idx=1; idx<initPathStr.length(); idx++) {
nextDirection = initPathStr.charAt(idx);
PathCandidate newPathCand = getPathCandWind(initPath, nextDirection, timeStep, turnLoss, startPos, endPos, distStartEndMeters);
initPath = newPathCand;
}
}
List<PathCandidate> allPaths = new ArrayList<PathCandidate>();
List<PathCandidate> trgPaths = new ArrayList<PathCandidate>();
allPaths.add(initPath);
TimedPosition tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'L').getA();
double tstDist1 = startPos.getDistance(tstPosition.getPosition()).getMeters();
tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'R').getA();
double tstDist2 = startPos.getDistance(tstPosition.getPosition()).getMeters();
double hrzBinSize = (tstDist1 + tstDist2)/2.0; // horizontal bin size in meters
if (debugMsgOn) {
System.out.println("Horizontal Bin Size: "+hrzBinSize);
}
//double oobFact = 0.75; // out-of-bounds factor
boolean reachedEnd = false;
int addSteps = 0;
int finalSteps = 0; // maximum number of additional steps after first target-path found
while ((!reachedEnd)||(addSteps<finalSteps)) {
if (reachedEnd) {
addSteps++;
}
// generate new candidates (inside regatta-area)
Util.Pair<List<PathCandidate>,List<PathCandidate>> newPaths = this.generateCandidate(allPaths, timeStep, turnLoss, startPos, middlePos, endPos, distStartEndMeters);
// select good candidates
List<PathCandidate> leftPaths = this.filterCandidates(newPaths.getA(), hrzBinSize/2.0);
List<PathCandidate> rightPaths = this.filterCandidates(newPaths.getB(), hrzBinSize/2.0);
List<PathCandidate> nextPaths = new ArrayList<PathCandidate> ();
nextPaths.addAll(leftPaths);
nextPaths.addAll(rightPaths);
allPaths = nextPaths;
if (this.gridStore) {
/*ArrayList<TimedPosition> isoChrone = new ArrayList<TimedPosition>();
for(PathCand curCand : allPaths) {
isoChrone.add(curCand.pos);
}
this.gridPositions.add(isoChrone);*/
this.gridPositions.add(allPaths);
}
// check if there are still paths in the regatta-area
if (allPaths.size() > 0) {
for(PathCandidate curPath : allPaths) {
// terminate path-search if paths are found that are close enough to target
if ((curPath.vrt > distStartEndMeters)) {
int curBin = (int)Math.round(Math.floor( (curPath.hrz + hrzBinSize/2.0) / hrzBinSize ));
if ((Math.abs(curBin) <= 2)) {
reachedEnd = true;
trgPaths.add(curPath); // add path to list of target-paths
}
}
}
} else {
// terminate path-search as no path inside regatta-area are left
reachedEnd = true;
}
}
if (this.gridStore) {
BufferedWriter outputCSV;
try {
outputCSV = new BufferedWriter(new FileWriter(this.gridFile));
outputCSV.write("step; lat; lng; time; side\n");
int stepCount = 0;
for(List<PathCandidate> isoChrone : this.gridPositions) {
stepCount++;
for(PathCandidate isoPos : isoChrone) {
outputCSV.write(""+stepCount+"; "+isoPos.pos.getPosition().getLatDeg()+"; "+isoPos.pos.getPosition().getLngDeg()+"; "+(isoPos.pos.getTimePoint().asMillis()/1000)+"; "+isoPos.sid+"\n");
}
}
outputCSV.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// if no target-paths were found, return empty path
if (trgPaths.size() == 0) {
//trgPaths = allPaths; // TODO: only for testing; remove lateron
TimedPositionWithSpeed curPosition = new TimedPositionWithSpeedImpl(startTime, startPos, null);
path.add(curPosition);
return new PathImpl(path, wf); // return empty path
}
// sort target-paths ascending by distance-to-target
Collections.sort(trgPaths);
// debug output
//if (debugMsgOn) {
for(PathCandidate curPath : trgPaths) {
logger.info("\nPath: " + curPath.path + "\n Time: " + (Math.round((curPath.pos.getTimePoint().asMillis()-startTime.asMillis())/1000.0/60.0*10.0)/10.0)+", Height: "+curPath.vrt+" of "+(Math.round(startPos.getDistance(endPos).getMeters()*100.0)/100.0)+", Dist: "+curPath.hrz+"m ~ "+(Math.round(curPath.pos.getPosition().getDistance(endPos).getMeters()*100.0)/100.0)+"m");
//System.out.print(""+curPath.path+": "+curPath.pos.getTimePoint().asMillis()+", "+curPath.pos.getPosition().getLatDeg()+", "+curPath.pos.getPosition().getLngDeg()+", ");
//System.out.println(" height:"+curPath.vrt+" of "+startPos.getDistance(endPos).getMeters()+", dist:"+curPath.hrz+" ~ "+curPath.pos.getPosition().getDistance(endPos));
}
//}
//
// fill gwt-path
//
// generate intermediate steps
bestCand = trgPaths.get(0); // target-path ending closest to target
TimedPositionWithSpeed curPosition = null;
char nextDirection = '0';
char prevDirection = '0';
for(int step=0; step<(bestCand.path.length()-1); step++) {
nextDirection = bestCand.path.charAt(step);
if (nextDirection == '0') {
curPosition = new TimedPositionWithSpeedImpl(startTime, startPos, null);
path.add(curPosition);
} else {
boolean sameBaseDirection = this.isSameDirection(prevDirection, nextDirection);
TimedPosition newPosition = this.getStep(curPosition, timeStep, turnLoss, sameBaseDirection, nextDirection).getA();
curPosition = new TimedPositionWithSpeedImpl(newPosition.getTimePoint(), newPosition.getPosition(), null);
path.add(curPosition);
}
prevDirection = nextDirection;
}
// add final position (rescaled before to end on height of target)
path.add(new TimedPositionWithSpeedImpl(bestCand.pos.getTimePoint(), bestCand.pos.getPosition(), null));
return new PathImpl(path, wf);
}
}
@@ -1,751 +0,0 @@
package com.sap.sailing.simulator.impl;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Logger;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.PolarDiagram;
import com.sap.sailing.simulator.SimulationParameters;
import com.sap.sailing.simulator.TimedPosition;
import com.sap.sailing.simulator.TimedPositionWithSpeed;
import com.sap.sailing.simulator.windfield.WindFieldGenerator;
import com.sap.sse.common.Util;
public class PathGeneratorTreeGrowWind3 extends PathGeneratorBase {
private static Logger logger = Logger.getLogger("com.sap.sailing");
private boolean debugMsgOn = false;
double oobFact = 0.75; // out-of-bounds factor
int maxTurns = 0;
boolean upwindLeg = false;
String initPathStr = "0";
PathCandidate bestCand = null;
long usedTimeStep = 0;
boolean gridStore = false;
ArrayList<List<PathCandidate>> gridPositions = null;
ArrayList<List<PathCandidate>> isocPositions = null;
String gridFile = null;
public PathGeneratorTreeGrowWind3(SimulationParameters params) {
this.parameters = params;
}
public void setEvaluationParameters(String startDirection, int maxTurns, String gridFile) {
if (startDirection != null) {
this.initPathStr = "0" + startDirection;
} else {
this.initPathStr = "0";
}
this.maxTurns = maxTurns;
this.gridFile = gridFile;
if (this.gridFile != null) {
this.gridStore = true;
this.gridPositions = new ArrayList<List<PathCandidate>>();
this.isocPositions = new ArrayList<List<PathCandidate>>();
} else {
this.gridStore = false;
this.gridPositions = null;
this.isocPositions = null;
}
}
class SortPathCandsAbsHorizontally implements Comparator<PathCandidate> {
@Override
public int compare(PathCandidate p1, PathCandidate p2) {
if (Math.abs(p1.hrz) == Math.abs(p2.hrz)) {
return 0;
} else {
return (Math.abs(p1.hrz) < Math.abs(p2.hrz) ? -1 : +1);
}
}
}
class SortPathCandsHorizontally implements Comparator<PathCandidate> {
@Override
public int compare(PathCandidate p1, PathCandidate p2) {
if (p1.hrz == p2.hrz) {
return 0;
} else {
return (p1.hrz < p2.hrz ? -1 : +1);
}
}
}
// getter for evaluating best path cand propoerties further
PathCandidate getBestCand() {
return this.bestCand;
}
long getUsedTimeStep() {
return this.usedTimeStep;
}
// generate step in one of the possible directions
// default: L - left, R - right
// extended: M - wide left, S - wide right
Util.Pair<TimedPosition,Wind> getStep(TimedPosition pos, long timeStep, long turnLoss, boolean sameBaseDirection, char nextDirection) {
double offDeg = 3.0;
WindFieldGenerator wf = this.parameters.getWindField();
TimePoint curTime = pos.getTimePoint();
Position curPosition = pos.getPosition();
Wind posWind = wf.getWind(new TimedPositionWithSpeedImpl(curTime, curPosition, null));
PolarDiagram pd = this.parameters.getBoatPolarDiagram();
pd.setWind(posWind);
Wind appWind = new WindImpl(posWind.getPosition(), posWind.getTimePoint(), pd.getWind());;
// get beat-angle left and right
Bearing travelBearing = null;
Bearing tmpBearing = null;
if (nextDirection == 'L') {
if (this.upwindLeg) {
travelBearing = pd.optimalDirectionsUpwind()[0];
} else {
travelBearing = pd.optimalDirectionsDownwind()[0];
}
} else if (nextDirection == 'R') {
if (this.upwindLeg) {
travelBearing = pd.optimalDirectionsUpwind()[1];
} else {
travelBearing = pd.optimalDirectionsDownwind()[1];
}
} else if (nextDirection == 'M') {
if (this.upwindLeg) {
tmpBearing = pd.optimalDirectionsUpwind()[0];
travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg));
} else {
tmpBearing = pd.optimalDirectionsDownwind()[0];
travelBearing = tmpBearing.add(new DegreeBearingImpl(-offDeg));
}
} else if (nextDirection == 'S') {
if (this.upwindLeg) {
tmpBearing = pd.optimalDirectionsUpwind()[1];
travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg));
} else {
tmpBearing = pd.optimalDirectionsDownwind()[1];
travelBearing = tmpBearing.add(new DegreeBearingImpl(+offDeg));
}
}
// determine beat-speed left and right
SpeedWithBearing travelSpeed = pd.getSpeedAtBearing(travelBearing);
TimePoint travelTime;
TimePoint nextTime = new MillisecondsTimePoint(curTime.asMillis()+timeStep);
if (sameBaseDirection) {
travelTime = nextTime;
} else {
travelTime = new MillisecondsTimePoint(nextTime.asMillis() - turnLoss);
}
return new Util.Pair<TimedPosition,Wind>(new TimedPositionImpl(nextTime, travelSpeed.travelTo(curPosition, curTime, travelTime)), appWind);
}
// use base direction to distinguish direction changes that do or don't require a turn
char getBaseDirection(char direction) {
char baseDirection = direction;
if (direction == 'M') {
baseDirection = 'L';
}
if (direction == 'S') {
baseDirection = 'R';
}
return baseDirection;
}
// check whether nextDirection is same base direction as previous direction, i.e. no turn
boolean isSameDirection(char prevDirection, char nextDirection) {
char prevBaseDirection = this.getBaseDirection(prevDirection);
char nextBaseDirection = this.getBaseDirection(nextDirection);
return ((nextBaseDirection == prevBaseDirection)||(prevBaseDirection == '0'));
}
// get path candidate measuring height towards (local, current-apparent) wind
PathCandidate getPathCandWind(PathCandidate path, char nextDirection, long timeStep, long turnLoss, Position posStart, Position posEnd, double tgtHeight) {
char prevDirection = path.path.charAt(path.path.length()-1);
boolean sameBaseDirection = this.isSameDirection(prevDirection, nextDirection);
int turnCount = path.trn;
if (!sameBaseDirection) {
turnCount++;
}
// calculate next path position (taking turn-loss into account)
Util.Pair<TimedPosition,Wind> nextStep = this.getStep(path.pos, timeStep, turnLoss, sameBaseDirection, nextDirection);
TimedPosition pathPos = nextStep.getA();
Wind posWind = nextStep.getB();
// calculate height-position with reference to race course
Position posHeight = pathPos.getPosition().projectToLineThrough(posEnd, posWind.getBearing());
Bearing bearVrt = posStart.getBearingGreatCircle(posEnd);
Position posHeightTrgt = pathPos.getPosition().projectToLineThrough(posStart, bearVrt);
// calculate vertical distance as distance of height-position to start
//double vrtDist = Math.round(posHeightTrgt.getDistance(posStart).getMeters()*100.0)/100.0;
Bearing bearHeight = posEnd.getBearingGreatCircle(posHeight);
double bearHeightSide = posWind.getBearing().getDifferenceTo(bearHeight).getDegrees();
/*if (Math.abs(bearHeightSide) > 5.0) {
System.out.println("bearHeightSide: "+bearHeightSide);
}*/
double vrtSide = (this.upwindLeg ? -1.0 : +1.0);
if (Math.abs(bearHeightSide) > 170.0) {
vrtSide = (this.upwindLeg ? +1.0 : -1.0);
}
double vrtDist = vrtSide*Math.round(posHeight.getDistance(posEnd).getMeters()*100.0)/100.0;
/*if (vrtDist > tgtHeight) {
// scale last step so that vrtDist ~ tgtHeight
Position prevPos = path.pos.getPosition();
TimePoint prevTime = path.pos.getTimePoint();
double heightFrac = (tgtHeight - path.vrt) / (vrtDist - path.vrt);
Position newPos = prevPos.translateGreatCircle(prevPos.getBearingGreatCircle(pathPos.getPosition()), prevPos.getDistance(pathPos.getPosition()).scale(heightFrac));
TimePoint newTime = new MillisecondsTimePoint(Math.round(prevTime.asMillis() + (pathPos.getTimePoint().asMillis()-prevTime.asMillis())*heightFrac));
pathPos = new TimedPositionImpl(newTime, newPos);
posHeight = pathPos.getPosition().projectToLineThrough(posStart, bearVrt);
}*/
// calculate horizontal side: left or right in reference to race course
double posSide = 1;
//double posBear = posWind.getBearing().getDegrees() - posEnd.getBearingGreatCircle(pathPos.getPosition()).getDegrees();
Bearing posBear = posStart.getBearingGreatCircle(pathPos.getPosition());
double posBearDiff = bearVrt.getDifferenceTo(posBear).getDegrees();
if ((posBearDiff < 0.0)||(posBearDiff > 180.0)) {
posSide = -1;
} else if ((posBearDiff == 0.0)||(posBearDiff == 180.0)) {
posSide = 0;
}
// calculate horizontal distance as distance of height-position to current position
//double hrzDist = Math.round(posSide*posHeight.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0;
double hrzDist = Math.round(posSide*posHeightTrgt.getDistance(pathPos.getPosition()).getMeters()*100.0)/100.0;
//System.out.println(""+hrzDist+", "+vrtDist+", "+pathPos.getPosition().getLatDeg()+", "+pathPos.getPosition().getLngDeg()+", "+posHeight.getLatDeg()+", "+posHeight.getLngDeg());
// extend path-string by step-direction
String pathStr = path.path + nextDirection;
char nextBaseDirection = this.getBaseDirection(nextDirection);
return (new PathCandidate(pathPos, vrtDist, hrzDist, turnCount, pathStr, nextBaseDirection, posWind));
}
// generate path candidates based on beat angles
List<PathCandidate> getPathCandsBeatWind(PathCandidate path, long timeStep, long turnLoss, Position posStart, Position posEnd, double tgtHeight) {
List<PathCandidate> result = new ArrayList<PathCandidate>();
PathCandidate newPathCand;
if (this.maxTurns > 0) {
char prevDirection = path.path.charAt(path.path.length()-1);
if ((path.trn < this.maxTurns)||(this.isSameDirection(prevDirection, 'L'))) {
newPathCand = getPathCandWind(path, 'L', timeStep, turnLoss, posStart, posEnd, tgtHeight);
result.add(newPathCand);
}
if ((path.trn < this.maxTurns)||(this.isSameDirection(prevDirection, 'R'))) {
newPathCand = getPathCandWind(path, 'R', timeStep, turnLoss, posStart, posEnd, tgtHeight);
result.add(newPathCand);
}
} else {
// step left
newPathCand = getPathCandWind(path, 'L', timeStep, turnLoss, posStart, posEnd, tgtHeight);
result.add(newPathCand);
// step wide left
//newPathCand = getPathCandWind(path, 'M', timeStep, turnLoss, posStart, posEnd, tgtHeight);
//result.add(newPathCand);
// step right
newPathCand = getPathCandWind(path, 'R', timeStep, turnLoss, posStart, posEnd, tgtHeight);
result.add(newPathCand);
// step wide right
//newPathCand = getPathCandWind(path, 'S', timeStep, turnLoss, posStart, posEnd, tgtHeight);
//result.add(newPathCand);
}
return result;
}
Util.Pair<List<PathCandidate>,List<PathCandidate>> generateCandidate(List<PathCandidate> oldPaths, long timeStep, long turnLoss, Position posStart, Position posMiddle, Position posEnd, double tgtHeight) {
List<PathCandidate> newPathCands;
List<PathCandidate> leftPaths = new ArrayList<PathCandidate>();
List<PathCandidate> rightPaths = new ArrayList<PathCandidate>();
for(PathCandidate curPath : oldPaths) {
newPathCands = this.getPathCandsBeatWind(curPath, timeStep, turnLoss, posStart, posEnd, tgtHeight);
for (PathCandidate curNewPath : newPathCands) {
// check whether path is *outside* regatta-area
double distFromMiddleMeters = posMiddle.getDistance(curPath.pos.getPosition()).getMeters();
if (distFromMiddleMeters > oobFact * tgtHeight) {
continue; // ignore curPath
}
if (curNewPath.sid == 'L') {
leftPaths.add(curNewPath);
} else if (curNewPath.sid == 'R') {
rightPaths.add(curNewPath);
}
}
}
Util.Pair<List<PathCandidate>,List<PathCandidate>> newPaths = new Util.Pair<List<PathCandidate>,List<PathCandidate>>(leftPaths, rightPaths);
return newPaths;
}
List<PathCandidate> filterCandidates(List<PathCandidate> allCands, double hrzBinWidth) {
boolean[] filterMap = new boolean[allCands.size()];
// sort candidates by horizontal distance
Comparator<PathCandidate> sortHorizontal = new SortPathCandsHorizontally();
Collections.sort(allCands, sortHorizontal);
// start scan with index 0
int idxL = 0;
int idxR = 0;
// for each candidate, check the neighborhoods and identify bad candidates
for(int idx = 0; idx < allCands.size(); idx++) {
// current horizontal distance
double hrzDist = allCands.get(idx).hrz;
// align left index
while(Math.abs(hrzDist - allCands.get(idxL).hrz) > hrzBinWidth) {
idxL++;
}
// align right index
boolean finished = false;
while(!finished && (idxR < (allCands.size()-1))) {
if (Math.abs(hrzDist - allCands.get(idxR+1).hrz) <= hrzBinWidth) {
idxR++;
} else {
finished = true;
}
}
// search maximum height
// in neighborhood idxL, ..., idxR
// init max for search
int vrtIdx = idxL;
double vrtMax = allCands.get(vrtIdx).vrt;
filterMap[vrtIdx] = false;
// evaluate remainder of neighborhood
if (idxL < idxR) {
for(int jdx = (idxL+1); jdx <= idxR; jdx++) {
if (allCands.get(jdx).vrt > vrtMax) {
// reset previous max candidate
filterMap[vrtIdx] = true;
// keep max height
vrtMax = allCands.get(jdx).vrt;
// keep max index
vrtIdx = jdx;
// set current max candidate
filterMap[vrtIdx] = false;
} else {
filterMap[jdx] = true;
}
}
}
} // endfor each candidate
// collect all good candidates (i.e. filterMap == false)
List<PathCandidate> filterCands = new ArrayList<PathCandidate>();
for(int idx=0; idx < allCands.size(); idx++) {
if (!filterMap[idx]) {
filterCands.add(allCands.get(idx));
}
}
// return remaining good candidates
return filterCands;
}
List<PathCandidate> filterIsochrone(List<PathCandidate> allCands, double hrzBinWidth) {
boolean[] filterMap = new boolean[allCands.size()];
for(int idx = 0; idx < allCands.size(); idx++) {
filterMap[idx] = true;
}
// sort candidates by horizontal distance
Comparator<PathCandidate> sortHorizontal = new SortPathCandsHorizontally();
Collections.sort(allCands, sortHorizontal);
// start scan with index 0
int idxL = 0;
int idxR = 0;
// for each candidate, check the neighborhoods and identify bad candidates
for(int idx = 0; idx < allCands.size(); idx++) {
// current horizontal distance
double hrzDist = allCands.get(idx).hrz;
// align left index
while(Math.abs(hrzDist - allCands.get(idxL).hrz) > hrzBinWidth) {
idxL++;
}
// align right index
boolean finished = false;
while(!finished && (idxR < (allCands.size()-1))) {
if (Math.abs(hrzDist - allCands.get(idxR+1).hrz) <= hrzBinWidth) {
idxR++;
} else {
finished = true;
}
}
// search maximum height
// in neighborhood idxL, ..., idxR
// init max for search
ArrayList<Integer> vrtIdx = new ArrayList<Integer>();
vrtIdx.add(idxL);
double vrtMax = allCands.get(idxL).vrt;
// evaluate remainder of neighborhood
if (idxL < idxR) {
for(int jdx = (idxL+1); jdx <= idxR; jdx++) {
if (allCands.get(jdx).vrt > vrtMax) {
// keep max height
vrtMax = allCands.get(jdx).vrt;
// keep max index
vrtIdx = new ArrayList<Integer>();
vrtIdx.add(jdx);
} else if (allCands.get(jdx).vrt == vrtMax) {
// add further max indexes
vrtIdx.add(jdx);
}
}
}
for(Integer jdx : vrtIdx) {
filterMap[jdx] = false;
}
} // endfor each candidate
// collect all good candidates (i.e. filterMap == false)
List<PathCandidate> filterCands = new ArrayList<PathCandidate>();
for(int idx=0; idx < allCands.size(); idx++) {
if (!filterMap[idx]) {
filterCands.add(allCands.get(idx));
}
}
// return remaining good candidates
return filterCands;
}
@Override
public Path getPath() {
WindFieldGenerator wf = this.parameters.getWindField();
PolarDiagram pd = this.parameters.getBoatPolarDiagram();
Position startPos = this.parameters.getCourse().get(0);
Position endPos = this.parameters.getCourse().get(1);
// test downwind: exchange start and end
//Position startPos = this.parameters.getCourse().get(1);
//Position endPos = this.parameters.getCourse().get(0);
TimePoint startTime = wf.getStartTime();// new MillisecondsTimePoint(0);
List<TimedPositionWithSpeed> path = new ArrayList<TimedPositionWithSpeed>();
Position currentPosition = startPos;
TimePoint currentTime = startTime;
Distance distStartEnd = startPos.getDistance(endPos);
double distStartEndMeters = distStartEnd.getMeters();
Wind wndStart = wf.getWind(new TimedPositionWithSpeedImpl(startTime, startPos, null));
logger.fine("wndStart speed:" + wndStart.getKnots() + " angle:" + wndStart.getBearing().getDegrees());
pd.setWind(wndStart);
Bearing bearVrt = startPos.getBearingGreatCircle(endPos);
//Bearing bearHrz = bearVrt.add(new DegreeBearingImpl(90.0));
Position middlePos = startPos.translateGreatCircle(bearVrt, distStartEnd.scale(0.5));
Bearing bearRCWind = wndStart.getBearing().getDifferenceTo(bearVrt);
String legType = "downwind";
this.upwindLeg = false;
if ((Math.abs(bearRCWind.getDegrees()) > 90.0)&&(Math.abs(bearRCWind.getDegrees()) < 270.0)) {
legType = "upwind";
this.upwindLeg = true;
}
if (debugMsgOn) {
System.out.println("start : "+startPos.getLatDeg()+", "+startPos.getLngDeg());
System.out.println("middle: "+middlePos.getLatDeg()+", "+middlePos.getLngDeg());
System.out.println("end : "+endPos.getLatDeg()+", "+endPos.getLngDeg());
}
logger.info("Leg Direction: "+legType);
long timeStep = wf.getTimeStep().asMillis()/2;
if (!this.upwindLeg) {
timeStep = timeStep / 2;
}
this.usedTimeStep = timeStep;
logger.info("Time step :" + timeStep);
long turnLoss = pd.getTurnLoss(); // 4000; // time lost when doing a turn
if (!this.upwindLeg) {
turnLoss = turnLoss / 2;
}
// calculate initial position according to initPathStr
PathCandidate initPath = new PathCandidate(new TimedPositionImpl(currentTime, currentPosition), 0.0, 0.0, 0, "0", '0', wndStart);
if (initPathStr.length()>1) {
char nextDirection = '0';
for(int idx=1; idx<initPathStr.length(); idx++) {
nextDirection = initPathStr.charAt(idx);
PathCandidate newPathCand = getPathCandWind(initPath, nextDirection, timeStep, turnLoss, startPos, endPos, distStartEndMeters);
initPath = newPathCand;
}
}
List<PathCandidate> allPaths = new ArrayList<PathCandidate>();
List<PathCandidate> trgPaths = new ArrayList<PathCandidate>();
allPaths.add(initPath);
TimedPosition tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'L').getA();
double tstDist1 = startPos.getDistance(tstPosition.getPosition()).getMeters();
tstPosition = this.getStep(new TimedPositionImpl(startTime, startPos), timeStep, turnLoss, true, 'R').getA();
double tstDist2 = startPos.getDistance(tstPosition.getPosition()).getMeters();
double hrzBinSize = (tstDist1 + tstDist2)/3.0; // horizontal bin size in meters
if (debugMsgOn) {
System.out.println("Horizontal Bin Size: "+hrzBinSize);
}
//double oobFact = 0.75; // out-of-bounds factor
boolean reachedEnd = false;
int addSteps = 0;
int finalSteps = 0; // maximum number of additional steps after first target-path found
while ((!reachedEnd)||(addSteps<finalSteps)) {
if (reachedEnd) {
addSteps++;
}
// generate new candidates (inside regatta-area)
Util.Pair<List<PathCandidate>,List<PathCandidate>> newPaths = this.generateCandidate(allPaths, timeStep, turnLoss, startPos, middlePos, endPos, distStartEndMeters);
// select good candidates
List<PathCandidate> leftPaths = this.filterCandidates(newPaths.getA(), hrzBinSize/2.0);
List<PathCandidate> rightPaths = this.filterCandidates(newPaths.getB(), hrzBinSize/2.0);
List<PathCandidate> nextPaths = new ArrayList<PathCandidate> ();
nextPaths.addAll(leftPaths);
nextPaths.addAll(rightPaths);
allPaths = nextPaths;
if (this.gridStore) {
/*ArrayList<TimedPosition> isoChrone = new ArrayList<TimedPosition>();
for(PathCand curCand : allPaths) {
isoChrone.add(curCand.pos);
}
this.gridPositions.add(isoChrone);*/
this.gridPositions.add(allPaths);
List<PathCandidate> isocPaths = this.filterIsochrone(allPaths, hrzBinSize);
this.isocPositions.add(isocPaths);
}
// check if there are still paths in the regatta-area
if (allPaths.size() > 0) {
for(PathCandidate curPath : allPaths) {
// terminate path-search if paths are found that are close enough to target
//if ((curPath.vrt > distStartEndMeters)) {
if ((curPath.vrt > 0.0)) {
//logger.info("\ntPath: " + curPath.path + "\n Time: " + (Math.round((curPath.pos.getTimePoint().asMillis()-startTime.asMillis())/1000.0/60.0*10.0)/10.0)+", Height: "+curPath.vrt+" of "+(Math.round(startPos.getDistance(endPos).getMeters()*100.0)/100.0)+", Dist: "+curPath.hrz+"m ~ "+(Math.round(curPath.pos.getPosition().getDistance(endPos).getMeters()*100.0)/100.0)+"m");
int curBin = (int)Math.round(Math.floor( (curPath.hrz + hrzBinSize/2.0) / hrzBinSize ));
if ((Math.abs(curBin) <= 1)) {
reachedEnd = true;
trgPaths.add(curPath); // add path to list of target-paths
}
}
}
} else {
// terminate path-search as no path inside regatta-area are left
reachedEnd = true;
}
}
if (this.gridStore) {
double distResolution = distStartEndMeters*0.01;
BufferedWriter outputCSV;
try {
outputCSV = new BufferedWriter(new FileWriter(this.gridFile+"-grid.csv"));
outputCSV.write("step; lat; lng; time; side; path; vrt\n");
outputCSV.write("0; "+startPos.getLatDeg()+"; "+startPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; "+(-distStartEndMeters)+"\n");
outputCSV.write("0; "+endPos.getLatDeg()+"; "+endPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; 0\n");
int stepCount = 0;
for(List<PathCandidate> isoChrone : this.gridPositions) {
stepCount++;
PathCandidate prevPos = null;
for(PathCandidate isoPos : isoChrone) {
if (prevPos != null) {
if (prevPos.pos.getPosition().getDistance(isoPos.pos.getPosition()).getMeters() < distResolution) {
continue;
}
}
String outStr = ""+stepCount+"; "+isoPos.pos.getPosition().getLatDeg()+"; "+isoPos.pos.getPosition().getLngDeg()+"; "+(isoPos.pos.getTimePoint().asMillis()/1000)+"; "+isoPos.sid;
outStr += "; "+isoPos.path+"; "+isoPos.vrt;
outStr += "\n";
outputCSV.write(outStr);
prevPos = isoPos;
}
}
outputCSV.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
outputCSV = new BufferedWriter(new FileWriter(this.gridFile+"-isoc.csv"));
outputCSV.write("step; lat; lng; time; side; path; vrt\n");
outputCSV.write("0; "+startPos.getLatDeg()+"; "+startPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; "+(-distStartEndMeters)+"\n");
outputCSV.write("0; "+endPos.getLatDeg()+"; "+endPos.getLngDeg()+"; "+(startTime.asMillis()/1000)+"; 0; 0; 0\n");
int stepCount = 0;
for(List<PathCandidate> isoChrone : this.isocPositions) {
stepCount++;
PathCandidate prevPos = null;
for(PathCandidate isoPos : isoChrone) {
if (prevPos != null) {
if (prevPos.pos.getPosition().getDistance(isoPos.pos.getPosition()).getMeters() < distResolution) {
continue;
}
}
String outStr = ""+stepCount+"; "+isoPos.pos.getPosition().getLatDeg()+"; "+isoPos.pos.getPosition().getLngDeg()+"; "+(isoPos.pos.getTimePoint().asMillis()/1000)+"; "+isoPos.sid;
outStr += "; "+isoPos.path+"; "+isoPos.vrt;
outStr += "\n";
outputCSV.write(outStr);
prevPos = isoPos;
}
}
outputCSV.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// if no target-paths were found, return empty path
if (trgPaths.size() == 0) {
//trgPaths = allPaths; // TODO: only for testing; remove lateron
TimedPositionWithSpeed curPosition = new TimedPositionWithSpeedImpl(startTime, startPos, null);
path.add(curPosition);
return new PathImpl(path, wf); // return empty path
}
// sort target-paths ascending by distance-to-target
Collections.sort(trgPaths);
// debug output
//if (debugMsgOn) {
for(PathCandidate curPath : trgPaths) {
logger.info("\nPath: " + curPath.path + "\n Time: " + (Math.round((curPath.pos.getTimePoint().asMillis()-startTime.asMillis())/1000.0/60.0*10.0)/10.0)+", Height: "+curPath.vrt+" of "+(Math.round(startPos.getDistance(endPos).getMeters()*100.0)/100.0)+", Dist: "+curPath.hrz+"m ~ "+(Math.round(curPath.pos.getPosition().getDistance(endPos).getMeters()*100.0)/100.0)+"m");
//System.out.print(""+curPath.path+": "+curPath.pos.getTimePoint().asMillis()+", "+curPath.pos.getPosition().getLatDeg()+", "+curPath.pos.getPosition().getLngDeg()+", ");
//System.out.println(" height:"+curPath.vrt+" of "+startPos.getDistance(endPos).getMeters()+", dist:"+curPath.hrz+" ~ "+curPath.pos.getPosition().getDistance(endPos));
}
//}
//
// fill gwt-path
//
// generate intermediate steps
bestCand = trgPaths.get(0); // target-path ending closest to target
TimedPositionWithSpeed curPosition = null;
char nextDirection = '0';
char prevDirection = '0';
for(int step=0; step<(bestCand.path.length()-1); step++) {
nextDirection = bestCand.path.charAt(step);
if (nextDirection == '0') {
curPosition = new TimedPositionWithSpeedImpl(startTime, startPos, null);
path.add(curPosition);
} else {
boolean sameBaseDirection = this.isSameDirection(prevDirection, nextDirection);
TimedPosition newPosition = this.getStep(curPosition, timeStep, turnLoss, sameBaseDirection, nextDirection).getA();
curPosition = new TimedPositionWithSpeedImpl(newPosition.getTimePoint(), newPosition.getPosition(), null);
path.add(curPosition);
}
prevDirection = nextDirection;
}
// add final position (rescaled before to end on height of target)
path.add(new TimedPositionWithSpeedImpl(bestCand.pos.getTimePoint(), bestCand.pos.getPosition(), null));
return new PathImpl(path, wf);
}
}
@@ -7,10 +7,23 @@ import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sse.common.Util;
public class RectangularBoundary implements Boundary {
/**
* Implements the {@link Grid} interface by providing a grid of GPS-positions based on the plane approximation of the
* earth and the corresponding index-calculation to associate an arbitrary GPS-position with the closest
* grid-GPS-position.
*
* Since the grid has rectangular shape in the plane approximation, we call it rectangular grid. The plane approximation
* allows for a simple index calculation based on CPU-light operations. If not aligned with North the grid gets slightly
* distorted leading to asymmetries which may influence the accuracy of path calculations or optimizations.
* Still, for typical race course diameters of up to 5 nautical miles, it is a very good and fast approximation.
*
* @author Christopher Ronnewinkel (D036654)
*
*/
public class RectangularGrid implements Grid {
private static final long serialVersionUID = 3598121983120213464L;
private Position rcStart; // start position of race course
@@ -24,7 +37,7 @@ public class RectangularBoundary implements Boundary {
private int borderX;
private Position northWest;
//private Position southEast;
// private Position southEast;
private Position southWest;
private Position northEast;
@@ -43,20 +56,17 @@ public class RectangularBoundary implements Boundary {
double tolerance;
public RectangularBoundary(Position p1, Position p2, double tlr) {
// System.out.println("Start:"+p1);
// System.out.println("End :"+p2);
public RectangularGrid(Position p1, Position p2) {
rcStart = p1;
rcEnd = p2;
tolerance = tlr;
tolerance = 0.1;
north = p1.getBearingGreatCircle(p2);
south = north.reverse();
east = north.add(TRUEEAST);
west = north.add(TRUEWEST);
east = north.add(relativeEast);
west = north.add(relativeWest);
appHeight = p1.getDistance(p2);
appWidth = appHeight.scale(2);
@@ -65,40 +75,21 @@ public class RectangularBoundary implements Boundary {
appNorthWest = p2.translateGreatCircle(west, appHeight);
appNorthEast = p2.translateGreatCircle(east, appHeight);
// the following test shows, that numerics appear to be instable:
// translating appNorthEast 2xEast should be appNorthWest, but numerically its NOT
// Position tstNorthEast = appNorthWest.translateGreatCircle(east, appHeight).translateGreatCircle(east,
// appHeight);
// appSouthEast = appNorthEast.translateGreatCircle(south, appHeight);
// appSouthWest = appNorthWest.translateGreatCircle(south, appHeight);
appSouthWest = p1.translateGreatCircle(west, appHeight);
appSouthEast = p1.translateGreatCircle(east, appHeight);
// System.out.println("southWest:"+appSouthWest);
// System.out.println("southEast:"+appSouthEast);
// System.out.println("northWest:"+appNorthWest);
// System.out.println("northEast:"+appNorthEast);
// System.out.println("testnEast:"+tstNorthEast);
Distance diag = appNorthWest.getDistance(appSouthEast);
Bearing diag1 = appSouthEast.getBearingGreatCircle(appNorthWest);
northWest = appNorthWest.translateGreatCircle(diag1, diag.scale(tolerance));
diag1 = diag1.reverse();
//southEast = appSouthEast.translateGreatCircle(diag1, diag.scale(tolerance));
// diag1 = diag1.reverse();
// southEast = appSouthEast.translateGreatCircle(diag1, diag.scale(tolerance));
Bearing diag2 = appSouthWest.getBearingGreatCircle(appNorthEast);
northEast = appNorthEast.translateGreatCircle(diag2, diag.scale(tolerance));
diag2 = diag2.reverse();
southWest = appSouthWest.translateGreatCircle(diag2, diag.scale(tolerance));
}
public RectangularBoundary(Position p1, Position p2) {
this(p1, p2, 0.1);
}
@Override
public Map<String, Position> getCorners() {
@@ -113,7 +104,7 @@ public class RectangularBoundary implements Boundary {
}
@Override
public boolean isWithinBoundaries(Position p) {
public boolean inBounds(Position p) {
Position northProjection = p.projectToLineThrough(northWest, getEast());
Position southProjection = p.projectToLineThrough(southWest, getEast());
@@ -131,14 +122,14 @@ public class RectangularBoundary implements Boundary {
}
@Override
public Position[][] extractGrid(int hPoints, int vPoints, int borderY, int borderX) {
public Position[][] generatePositions(int hPoints, int vPoints, int borderY, int borderX) {
this.vPoints = vPoints;
this.hPoints = hPoints;
this.borderY = borderY;
this.borderX = borderX;
double xscale = 1.5;
double alat = (rcEnd.getLatDeg() + rcStart.getLatDeg()) / 2.;
@@ -170,11 +161,9 @@ public class RectangularBoundary implements Boundary {
nHor[0] = nscHor[0];
nHor[1] = nscHor[1] / lngScale;
// System.out.println("LngScale:"+lngScale+", Diff.Vrt:"+dVrt);
Position[][] grid = new Position[vPoints+2*borderY][hPoints+2*borderX];
Position[][] grid = new Position[vPoints + 2 * borderY][hPoints + 2 * borderX];
Position pv;
for (int i = -borderY; i < (vPoints+borderY); i++) {
// System.out.print("nrow "+i+": ");
for (int i = -borderY; i < (vPoints + borderY); i++) {
if (i == 0) {
pv = rcStart;
@@ -187,41 +176,32 @@ public class RectangularBoundary implements Boundary {
int j = 0;
// left side
while (j < (hPoints+2*borderX) / 2) {
grid[i+borderY][j] = new DegreePosition(pv.getLatDeg() - ((hPoints + 2*borderX - 1) / 2. - j) / (hPoints - 1) * nHor[0]
* xscale * lscVrt, pv.getLngDeg() - ((hPoints + 2*borderX - 1) / 2. - j) / (hPoints - 1) * nHor[1] * xscale
* lscVrt);
// System.out.print(""+grid[i][j]+"("+((hPoints-1)/2.-j)+"), ");
while (j < (hPoints + 2 * borderX) / 2) {
grid[i + borderY][j] = new DegreePosition(pv.getLatDeg() - ((hPoints + 2 * borderX - 1) / 2. - j)
/ (hPoints - 1) * nHor[0] * xscale * lscVrt, pv.getLngDeg()
- ((hPoints + 2 * borderX - 1) / 2. - j) / (hPoints - 1) * nHor[1] * xscale * lscVrt);
j++;
}
// middle
if ((hPoints+2*borderX) % 2 == 1) {
grid[i+borderY][j] = pv;
// System.out.print(""+grid[i][j]+", ");
if ((hPoints + 2 * borderX) % 2 == 1) {
grid[i + borderY][j] = pv;
j++;
}
// right side
while (j < (hPoints+2*borderX)) {
grid[i+borderY][j] = new DegreePosition(pv.getLatDeg() + (j - (hPoints + 2*borderX - 1) / 2.) / (hPoints - 1) * nHor[0]
* xscale * lscVrt, pv.getLngDeg() + (j - (hPoints + 2*borderX - 1) / 2.) / (hPoints - 1) * nHor[1] * xscale
* lscVrt);
// System.out.print(""+grid[i][j]+"("+(j-(hPoints-1)/2.)+"), ");
while (j < (hPoints + 2 * borderX)) {
grid[i + borderY][j] = new DegreePosition(pv.getLatDeg() + (j - (hPoints + 2 * borderX - 1) / 2.)
/ (hPoints - 1) * nHor[0] * xscale * lscVrt, pv.getLngDeg()
+ (j - (hPoints + 2 * borderX - 1) / 2.) / (hPoints - 1) * nHor[1] * xscale * lscVrt);
j++;
}
// System.out.println();
}
/*
* System.out.println("Grid Index Test:"); for (int i =0; i<vPoints; i++) { for (int j=0; j<hPoints; j++) {
* System.out.print(""+this.getGridIndex(grid[i][j])+", "); } System.out.println(); }
*/
return grid;
}
public Util.Pair<Integer, Integer> getGridIndex(Position x) {
public Util.Pair<Integer, Integer> getIndex(Position x) {
double[] scX = new double[2];
scX[0] = x.getLatDeg() - rcStart.getLatDeg();
@@ -229,64 +209,35 @@ public class RectangularBoundary implements Boundary {
double sPrd = scX[0] * nvHor[0] + scX[1] * nvHor[1];
int vIdx = Math.min(Math.max(-this.borderY, (int) Math.round(scX[0] * nvVrt[0] + scX[1] * nvVrt[1])), vPoints - 1 + this.borderY);
int hIdx = Math.min(Math.max(-this.borderX, (int) Math.round(sPrd + (hPoints - 1) / 2.)), hPoints - 1 + this.borderX);
int vIdx = Math.min(Math.max(-this.borderY, (int) Math.round(scX[0] * nvVrt[0] + scX[1] * nvVrt[1])), vPoints
- 1 + this.borderY);
int hIdx = Math.min(Math.max(-this.borderX, (int) Math.round(sPrd + (hPoints - 1) / 2.)), hPoints - 1
+ this.borderX);
// System.out.println("getGridIndex: "+vIdx+","+hIdx+"("+vPoints+","+hPoints+")");
return new Util.Pair<Integer, Integer>(vIdx, hIdx);
}
/*
* @Override public List<Position> extractLattice(int hPoints, int vPoints) {
*
* Position[][] grid = extractGrid(hPoints, vPoints); List<Position> lst = new ArrayList<Position>(); for(Position[]
* line : grid) { for(Position p : line) { lst.add(p); } } return lst; }
*
* @Override //may not return a rectangular lattice! public List<Position> extractLattice(Distance hStep, Distance
* vStep) {
*
* Position startPoint = appSouthWest;
*
* Bearing vBearing = getNorth(); Bearing hBearing = getEast(); boolean hMode = true;
*
* List<Position> lst = new ArrayList<Position>();
*
* Position current = startPoint; lst.add(current); Position next;
*
* while (true) {
*
* if (hMode) next = current.translateGreatCircle(hBearing, hStep); else next =
* current.translateGreatCircle(vBearing, vStep);
*
* if (isWithinBoundaries(next)) { current = next; lst.add(current); if (!hMode) { hMode = true; hBearing =
* hBearing.reverse(); } } else { if(hMode) hMode = false; else break; }
*
* }
*
* return lst; }
*/
@Override
public int getResY() {
return this.vPoints;
return this.vPoints;
}
@Override
public int getResX() {
return this.hPoints;
return this.hPoints;
}
@Override
public int getBorderY() {
return this.borderY;
return this.borderY;
}
@Override
public int getBorderX() {
return this.borderX;
return this.borderX;
}
@Override
public Bearing getNorth() {
return north;
@@ -317,32 +268,4 @@ public class RectangularBoundary implements Boundary {
return appHeight;
}
@Override
public Map<String, Double> getRelativeCoordinates(Position p) {
if (!isWithinBoundaries(p))
return null;
Map<String, Double> map = new HashMap<String, Double>();
Position px = p.projectToLineThrough(appSouthWest, getEast());
Position py = p.projectToLineThrough(appSouthWest, getNorth());
map.put("X", px.getDistance(appSouthWest).getMeters() / getWidth().getMeters());
map.put("Y", py.getDistance(appSouthWest).getMeters() / getHeight().getMeters());
return map;
}
@Override
public Position getRelativePoint(double x, double y) {
Position point = appSouthWest;
point = point.translateGreatCircle(getEast(), getWidth().scale(x));
point = point.translateGreatCircle(getNorth(), getHeight().scale(y));
return point;
}
@Override
public double getTolerance() {
return tolerance;
}
}
@@ -12,7 +12,7 @@ import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.RaceProperties;
import com.sap.sailing.simulator.SailingSimulator;
@@ -79,7 +79,7 @@ public class SailingSimulatorImpl implements SailingSimulator {
LOGGER.info("showOpportunist: "+this.simulationParameters.showOpportunist());
if (gridArea != null) {
Boundary bd = new RectangularBoundary(gridArea[0], gridArea[1], 0.1);
Grid bd = new CurvedGrid(gridArea[0], gridArea[1]);
// set base wind bearing
wf.getWindParameters().baseWindBearing += bd.getSouth().getDegrees();
@@ -103,7 +103,7 @@ public class SailingSimulatorImpl implements SailingSimulator {
}
wf.setBoundary(bd);
Position[][] positionGrid = bd.extractGrid(gridRes[0], gridRes[1], gridRes[2], gridRes[3]);
Position[][] positionGrid = bd.generatePositions(gridRes[0], gridRes[1], gridRes[2], gridRes[3]);
wf.setPositionGrid(positionGrid);
wf.generate(wf.getStartTime(), wf.getEndTime(), wf.getTimeStep());
}
@@ -113,7 +113,7 @@ public class SailingSimulatorImpl implements SailingSimulator {
//
// get instance of heuristic searcher
PathGeneratorTreeGrowWind3 genTreeGrow = new PathGeneratorTreeGrowWind3(this.simulationParameters);
PathGeneratorTreeGrowWind genTreeGrow = new PathGeneratorTreeGrowWind(this.simulationParameters);
// search best left-starting 1-turner
genTreeGrow.setEvaluationParameters("L", 1, null);
@@ -5,7 +5,7 @@ import java.util.List;
import java.util.Map;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.PolarDiagram;
import com.sap.sailing.simulator.SimulationParameters;
import com.sap.sailing.simulator.windfield.WindFieldGenerator;
@@ -64,8 +64,8 @@ public class SimulationParametersImpl implements SimulationParameters {
}
@Override
public Boundary getBoundaries() {
return windField.getBoundaries();
public Grid getGrid() {
return windField.getGrid();
}
@Override
@@ -373,7 +373,7 @@ public class SimulatorUtils {
Map<String, Path> paths = new HashMap<String, Path>();
// get instance of heuristic searcher
PathGeneratorTreeGrowWind3 genTreeGrow = new PathGeneratorTreeGrowWind3(parameters);
PathGeneratorTreeGrowWind genTreeGrow = new PathGeneratorTreeGrowWind(parameters);
// search best left-starting 1-turner
genTreeGrow.setEvaluationParameters("L", 1, null);
@@ -3,7 +3,7 @@ package com.sap.sailing.simulator.windfield;
import java.io.Serializable;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.TimedPosition;
@@ -11,7 +11,7 @@ public interface WindField extends Serializable {
public Wind getWind(TimedPosition coordinates);
public Boundary getBoundaries();
public Grid getGrid();
public Path getLine(TimedPosition seed, boolean forward);
@@ -7,13 +7,13 @@ import java.io.Serializable;
import com.sap.sailing.domain.common.Duration;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
public interface WindFieldGenerator extends WindField, Serializable {
public WindControlParameters getWindParameters();
public void setBoundary(Boundary boundary);
public void setBoundary(Grid boundary);
public void setPositionGrid(Position[][] positions);
@@ -1,12 +1,12 @@
package com.sap.sailing.simulator.windfield;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.windfield.impl.WindFieldGeneratorFactoryImpl;
public interface WindFieldGeneratorFactory {
static WindFieldGeneratorFactory INSTANCE = new WindFieldGeneratorFactoryImpl();
public WindFieldGenerator createWindFieldGenerator(String patternName, Boundary boundary,
public WindFieldGenerator createWindFieldGenerator(String patternName, Grid boundary,
WindControlParameters windParameters);
}
@@ -14,7 +14,7 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.TimedPosition;
import com.sap.sailing.simulator.windfield.WindControlParameters;
import com.sap.sailing.simulator.windfield.WindFieldGenerator;
@@ -32,7 +32,7 @@ public class WindFieldGeneratorBlastImpl extends WindFieldGeneratorImpl implemen
private double blastSizeProbability = 70;
private double blastEdgeProbability = 30;
private double blastBearingMean = 0;
private double blastBearingVar = 8;
private double blastBearingVar = 6;
private double defaultWindSpeed = 0;
private double defaultWindBearing = 0;
private SpeedWithBearing defaultSpeedWithBearing;
@@ -44,7 +44,7 @@ public class WindFieldGeneratorBlastImpl extends WindFieldGeneratorImpl implemen
private static Logger logger = Logger.getLogger(WindFieldGeneratorBlastImpl.class.getName());
public WindFieldGeneratorBlastImpl(Boundary boundary, WindControlParameters windParameters) {
public WindFieldGeneratorBlastImpl(Grid boundary, WindControlParameters windParameters) {
super(boundary, windParameters);
}
@@ -164,8 +164,7 @@ public class WindFieldGeneratorBlastImpl extends WindFieldGeneratorImpl implemen
//System.out.println("blast speed mean: "+bSpeedMean+" var: "+bSpeedVar);
//return NormalGen.nextDouble(new LFSR113("BlastSpeedStream"), bSpeedMean, bSpeedVar);
RandomStream speedStream = windParameters.getBlastRandomStreamManager().getRandomStream(BlastRandomSeedManagerImpl.BlastStream.SPEED.name());
return NormalGen.nextDouble(speedStream, bSpeedMean, bSpeedVar);
return Math.max(0.5*bSpeedMean, Math.min(1.5*bSpeedMean, NormalGen.nextDouble(speedStream, bSpeedMean, bSpeedVar)));
}
private int getBlastSize() {
@@ -177,8 +176,7 @@ public class WindFieldGeneratorBlastImpl extends WindFieldGeneratorImpl implemen
private double getBlastAngle() {
//return NormalGen.nextDouble(new LFSR113("BlastAngleStream"), blastBearingMean, blastBearingVar);
RandomStream bearingStream = windParameters.getBlastRandomStreamManager().getRandomStream(BlastRandomSeedManagerImpl.BlastStream.BEARING.name());
return NormalGen.nextDouble(bearingStream, blastBearingMean, blastBearingVar);
return Math.max(-1.5*blastBearingVar, Math.min(1.5*blastBearingVar, NormalGen.nextDouble(bearingStream, blastBearingMean, blastBearingVar)));
}
private SpeedWithBearing getSpeedWithBearing(TimedPosition timedPosition) {
@@ -7,7 +7,7 @@ import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.TimedPosition;
import com.sap.sailing.simulator.windfield.WindControlParameters;
import com.sap.sailing.simulator.windfield.WindFieldGenerator;
@@ -18,14 +18,14 @@ public class WindFieldGeneratorCombined extends WindFieldGeneratorImpl implement
private WindFieldGeneratorBlastImpl blastGen;
private WindFieldGeneratorOscillationImpl oscillationGen;
public WindFieldGeneratorCombined(Boundary boundary, WindControlParameters windParameters) {
public WindFieldGeneratorCombined(Grid boundary, WindControlParameters windParameters) {
super(boundary, windParameters);
blastGen = new WindFieldGeneratorBlastImpl(boundary, windParameters);
oscillationGen = new WindFieldGeneratorOscillationImpl(boundary, windParameters);
}
@Override
public void setBoundary(Boundary boundary) {
public void setBoundary(Grid boundary) {
super.setBoundary(boundary);
blastGen.setBoundary(boundary);
@@ -1,7 +1,7 @@
package com.sap.sailing.simulator.windfield.impl;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.windfield.WindControlParameters;
import com.sap.sailing.simulator.windfield.WindFieldGenerator;
import com.sap.sailing.simulator.windfield.WindFieldGeneratorFactory;
@@ -9,7 +9,7 @@ import com.sap.sailing.simulator.windfield.WindFieldGeneratorFactory;
public class WindFieldGeneratorFactoryImpl implements WindFieldGeneratorFactory {
@Override
public WindFieldGenerator createWindFieldGenerator(String patternName, Boundary boundary,
public WindFieldGenerator createWindFieldGenerator(String patternName, Grid boundary,
WindControlParameters windParameters) {
if (patternName.equals("BLASTS")) {
return new WindFieldGeneratorBlastImpl(boundary, windParameters);
@@ -13,7 +13,7 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.TimedPosition;
import com.sap.sailing.simulator.TimedPositionWithSpeed;
@@ -27,7 +27,7 @@ import com.sap.sse.common.Util;
public abstract class WindFieldGeneratorImpl implements WindFieldGenerator {
private static final long serialVersionUID = -6366698648104363491L;
protected Boundary boundary;
protected Grid boundary;
protected WindControlParameters windParameters;
protected Position[][] positions;
@@ -46,7 +46,7 @@ public abstract class WindFieldGeneratorImpl implements WindFieldGenerator {
private static Logger logger = Logger.getLogger("com.sap.sailing.windfield");
public WindFieldGeneratorImpl(Boundary boundary, WindControlParameters windParameters) {
public WindFieldGeneratorImpl(Grid boundary, WindControlParameters windParameters) {
this.boundary = boundary;
this.windParameters = windParameters;
this.positions = null;
@@ -58,7 +58,7 @@ public abstract class WindFieldGeneratorImpl implements WindFieldGenerator {
}
@Override
public void setBoundary(Boundary boundary) {
public void setBoundary(Grid boundary) {
this.boundary = boundary;
}
@@ -77,7 +77,7 @@ public abstract class WindFieldGeneratorImpl implements WindFieldGenerator {
}
@Override
public Boundary getBoundaries() {
public Grid getGrid() {
return boundary;
}
@@ -97,7 +97,7 @@ public abstract class WindFieldGeneratorImpl implements WindFieldGenerator {
LinkedList<TimedPositionWithSpeed> path = new LinkedList<TimedPositionWithSpeed>();
path.add(new TimedPositionWithSpeedImpl(currentTime, currentPosition, null));
while(boundary.isWithinBoundaries(currentPosition)) {
while(boundary.inBounds(currentPosition)) {
Wind currentWind = this.getWind(new TimedPositionImpl(startTime, currentPosition));
TimePoint middleTime = currentTime.plus(timeStep/2);
Position middlePosition;
@@ -138,7 +138,7 @@ public abstract class WindFieldGeneratorImpl implements WindFieldGenerator {
}
public Util.Pair<Integer, Integer> getPositionIndex(Position p) {
Util.Pair<Integer, Integer> gIdx = boundary.getGridIndex(p);
Util.Pair<Integer, Integer> gIdx = boundary.getIndex(p);
if ((gIdx.getA() != null) && (gIdx.getB() != null)) {
return gIdx;
} else {
@@ -11,7 +11,7 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.Path;
import com.sap.sailing.simulator.TimedPosition;
import com.sap.sailing.simulator.TimedPositionWithSpeed;
@@ -30,7 +30,7 @@ public class WindFieldGeneratorMeasured extends WindFieldGeneratorImpl implement
// super();
// }
public WindFieldGeneratorMeasured(Boundary boundary, WindControlParameters windParameters) {
public WindFieldGeneratorMeasured(Grid boundary, WindControlParameters windParameters) {
super(boundary, windParameters);
}
@@ -14,7 +14,7 @@ import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.simulator.Boundary;
import com.sap.sailing.simulator.Grid;
import com.sap.sailing.simulator.TimedPosition;
import com.sap.sailing.simulator.windfield.WindControlParameters;
import com.sap.sailing.simulator.windfield.WindFieldGenerator;
@@ -32,7 +32,7 @@ public class WindFieldGeneratorOscillationImpl extends WindFieldGeneratorImpl im
private static Logger logger = Logger.getLogger(WindFieldGeneratorOscillationImpl.class.getName());
public WindFieldGeneratorOscillationImpl(Boundary boundary, WindControlParameters windParameters) {
public WindFieldGeneratorOscillationImpl(Grid boundary, WindControlParameters windParameters) {
super(boundary, windParameters);
}
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: TracTrac Client module
Bundle-SymbolicName: com.tractrac.clientmodule
Bundle-Version: 3.0.1
Bundle-Version: 3.0.2
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Bundle-ClassPath: .,
lib/TracAPI.jar
@@ -12,6 +12,9 @@ Export-Package: com.tractrac.asio.lib.api,
com.tractrac.asio.lib.api.protocol.session,
com.tractrac.asio.lib.api.record,
com.tractrac.asio.lib.api.session,
com.tractrac.asio.lib.spi,
com.tractrac.asio.lib.spi.record,
com.tractrac.common.lib.api.messenger,
com.tractrac.common.lib.api.observer,
com.tractrac.common.lib.api.service,
com.tractrac.dataflow.lib.api,
@@ -21,6 +24,20 @@ Export-Package: com.tractrac.asio.lib.api,
com.tractrac.dataflow.lib.api.live.server,
com.tractrac.dataflow.lib.api.protocol,
com.tractrac.dataflow.lib.api.stored,
com.tractrac.dataflow.lib.impl,
com.tractrac.dataflow.lib.prov.live.ascii,
com.tractrac.dataflow.lib.prov.live.provider,
com.tractrac.dataflow.lib.prov.live.server,
com.tractrac.dataflow.lib.prov.live.tcp,
com.tractrac.dataflow.lib.prov.live.tcp.provider,
com.tractrac.dataflow.lib.prov.live.tcp.server,
com.tractrac.dataflow.lib.prov.stored,
com.tractrac.dataflow.lib.prov.stored.jdbc,
com.tractrac.dataflow.lib.prov.stored.stream,
com.tractrac.dataflow.lib.prov.stored.tcp,
com.tractrac.dataflow.lib.prov.stored.url,
com.tractrac.dataflow.lib.spi,
com.tractrac.dataflow.lib.spi.authentication,
com.tractrac.model.lib.api,
com.tractrac.model.lib.api.attachment,
com.tractrac.model.lib.api.data,
@@ -28,11 +45,30 @@ Export-Package: com.tractrac.asio.lib.api,
com.tractrac.model.lib.api.metadata,
com.tractrac.model.lib.api.route,
com.tractrac.model.lib.api.spatial,
com.tractrac.model.lib.impl,
com.tractrac.model.lib.impl.attachment,
com.tractrac.model.lib.impl.data,
com.tractrac.model.lib.impl.event,
com.tractrac.model.lib.impl.event.json,
com.tractrac.model.lib.impl.metadata,
com.tractrac.model.lib.impl.route,
com.tractrac.model.lib.impl.spatial,
com.tractrac.osgi,
com.tractrac.subscription.app.tracapi,
com.tractrac.subscription.lib.api,
com.tractrac.subscription.lib.api.competitor,
com.tractrac.subscription.lib.api.control,
com.tractrac.subscription.lib.api.event,
com.tractrac.subscription.lib.api.race,
com.tractrac.subscription.lib.impl,
com.tractrac.subscription.lib.impl.competitor,
com.tractrac.subscription.lib.impl.control,
com.tractrac.subscription.lib.impl.event,
com.tractrac.subscription.lib.impl.gms,
com.tractrac.subscription.lib.impl.io,
com.tractrac.subscription.lib.impl.io.controllers,
com.tractrac.subscription.lib.impl.io.receivers,
com.tractrac.subscription.lib.impl.race,
com.tractrac.util.lib.api,
com.tractrac.util.lib.api.autolog,
com.tractrac.util.lib.api.classloader,
@@ -40,17 +76,18 @@ Export-Package: com.tractrac.asio.lib.api,
com.tractrac.util.lib.api.enumerable,
com.tractrac.util.lib.api.exceptions,
com.tractrac.util.lib.api.image,
com.tractrac.util.lib.api.priorityqueue,
com.tractrac.util.lib.api.map,
com.tractrac.util.lib.api.programparameters,
com.tractrac.util.lib.api.search,
com.tractrac.util.lib.api.selector,
com.tractrac.util.lib.api.seqtimedatatable,
com.tractrac.util.lib.api.seqtimedatatable.btreemem,
com.tractrac.util.lib.api.seqtimedatatable.btreememdisk,
com.tractrac.util.lib.api.seqtimedatatable.simple,
com.tractrac.util.lib.api.serialize,
com.tractrac.util.lib.api.sortedset,
com.tractrac.util.lib.api.sortedset.btree
com.tractrac.util.lib.impl.autolog,
com.tractrac.util.lib.impl.autolog.file,
com.tractrac.util.lib.impl.autolog.log4j,
com.tractrac.util.lib.impl.autolog.multiple,
com.tractrac.util.lib.impl.autolog.println,
com.tractrac.util.lib.impl.classloader,
com.tractrac.util.lib.impl.programparameters
Bundle-ActivationPolicy: lazy
Bundle-Vendor: TracTrac
Import-Package: org.osgi.framework
+17
View File
@@ -16,6 +16,23 @@ It contains also some files:
- test.sh -> script that compiles the code in the src folder, creates the test.jar library and execute the code of the example.
- Manifest.txt -> manifest used to create the test.jar file
********************************************
************* TracAPI 3.0.2 ****************
********************************************
This is a final version. Only fixes bugs in the implementation
1) New features
- The IRacesListener.updateRace() event is thrown when the live delay for a race has changed. The IRace.getLiveDelay() method
returns the new value.
2) Bugs
- The events IRacesListener.abandonRace() and IRacesListener.startTracking() have been reviewed. Now they are always sent
when the races are updated either using the event manager or using the external JSON service "update_race_status"
- The controls are not updated when they are retrieved a second time from a parameters file. The library always returns
the first control thas was read the first time.
********************************************
************* TracAPI 3.0.1 ****************
********************************************
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -8,6 +8,6 @@
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.tractrac.clientmodule</artifactId>
<version>3.0.1</version>
<version>3.0.2</version>
<packaging>eclipse-plugin</packaging>
</project>
File mode changed.
File mode changed.
@@ -12,6 +12,7 @@ public class BoatClassSeriesFleet {
private BoatClass boatClass;
private SeriesBase series;
private int seriesOrder;
private int fleetOrder;
private Fleet fleet;
private static BoatClass getBoatClassForRace(ManagedRace managedRace) {
@@ -29,9 +30,14 @@ public class BoatClassSeriesFleet {
this.boatClass = getBoatClassForRace(race);
this.series = race.getSeries();
this.seriesOrder = getSeriesIndex(race, series);
this.fleetOrder = getFleetIndex(series.getFleets(), race.getFleet());
this.fleet = race.getFleet();
}
private int getFleetIndex(Iterable<? extends Fleet> fleets, Fleet fleet) {
return Util.indexOf(fleets, fleet);
}
public String getBoatClassName() {
return boatClass.getName();
}
@@ -86,6 +92,7 @@ public class BoatClassSeriesFleet {
result = prime * result + ((fleet == null) ? 0 : fleet.hashCode());
result = prime * result + ((series == null) ? 0 : series.hashCode());
result = prime * result + seriesOrder;
result = prime * result + fleetOrder;
return result;
}
@@ -115,7 +122,13 @@ public class BoatClassSeriesFleet {
return false;
if (seriesOrder != other.seriesOrder)
return false;
if (fleetOrder != other.fleetOrder)
return false;
return true;
}
public int getFleetOrder() {
return fleetOrder;
}
}
@@ -20,7 +20,7 @@ public class BoatClassSeriesDataFleetComparator implements Comparator<BoatClassS
if (result == 0) {
result = nameComparator.compare(left.getSeriesName(), right.getSeriesName());
if (result == 0) {
result = Integer.valueOf(left.getFleetOrdering()).compareTo(right.getFleetOrdering());
result = Integer.valueOf(left.getFleetOrder()).compareTo(right.getFleetOrder());
if (result == 0) {
result = nameComparator.compare(left.getFleetName(), right.getFleetName());
}
+159 -93
View File
@@ -1,93 +1,159 @@
# Replicating an SAP Sailing Analytics Server
[[_TOC_]]
### Requirements
Scaling to many (>100) concurrent users following a live sailing race requires providing more than one OSGi server instance. To an extent this is cause by an aspect of our architecture that decides how clients access server data. We use the Google Web Toolkit's (GWT) Remote Procedure Call (RPC) to let the clients request data from the server. These are handled as HTTP POST requests with a servlet composing the response message. This approach makes caching servlet responses to similar requests by a generic HTTP caching / proxy infrastructure difficult.
Using more than one server instance is additionally motivated by the ManyPlayers client architecture. ManyPlayers wants to use our code base but run it on their premises and only be assured that once their instance connects to our "master" instance then all relevant state will be replicated to their instance.
### Server State
The server's state at the current time roughly consists of the following:
* list of races with their tracking data loaded by the server
* a race's state consists of
* RaceDefinition object with name, boat class and event association
* Course object with Waypoints and ControlPoints
* a tracked race's state consisting of
* wind tracks
* raw boat position tracks
* mark position tracks
* mark passings
* race tracking start/finish times
* list of leaderboard groups with their names and descriptions
* list of leaderboards and their association with leaderboard groups
* leaderboard names, columns and the columns' associated tracked races, if any
* discarding rules
* competitor renamings
* explicit score corrections
* disqualification / "max-points" reasons (DSQ, OCS, DNS, DNF, DND, ...)
### Reliability
When a server is configured to be a replica of another "master" server, all updates to the master server must be replicated as quickly as possible to the replica until the replica permanently disconnects from the master server. If the connection breaks temporarily and is restored at a later point in time, all updates that occurred since the connection broke need to be updated to the client.
### Connectivity
Replicated instances may be able to only connect to the master through an HTTP connection. In this case, the master server needs to push updates through an HTTP connection reliably.
### Single Master, Multiple Replicas
For Kiel Week 2012 we want to support a single master server with multiple replicated copies, so-called "replica instances." The replicas are configured for their master node. This configuration doesn't change over the replica's life time. If the master dies, it need to be started again. Replicas then need to recover and re-sync once the new master instance is alive again.
### Concurrency Challenges
Different types of updates to the master server can happen concurrently. Tracking data for multiple races is being received in multiple threads which can execute truly concurrently on a multi-core architecture. Wind data may be received in parallel to receiving other tracking data. Leaderboard and LeaderboardGroup configurations may happen concurrently to all other types of changes. New races may start to get tracked concurrently to the many other server operations. Do we need to ensure that all these events whose effects need to be replicated have to be serialized in an order that is kept the same for all replica to ensure equal resulting states in all replica? What would serializing all these many concurrent events mean to the server's internal concurrency which is important to improve performance?
### Thoughts on Implementation
HTTP push protocols are implemented, e.g., by the Atmosphere framework and by our own, proprietary AbstractHttpPostServlet in the com.sap.sailing.server package. They both use a heartbeat mechanism to re-establish the HTTP connection after it got disconnected. However, both currently don't handle reliable data transmission. If a client is disconnected and re-connects later, it may have missed a number or messages sent while it was disconnected. This would need to be overcome in order to achieve the reliabiliy required for our replication scenario.
### Initial Load
When a replica is first set up and connected to a master server, the master server's current state at some consistent snapshot needs to be replicated to the replica and from then on be kept in sync. How can we produce such a consistent snapshot while understanding which subsequent change events to broadcast to which client?
One approach we're currently testing is using Java Serialization to copy an initial "image" of one server to the next. There are several tricky issues with this approach:
* listeners / observers: several classes offer listeners to be attached; for example, a DynamicTrackedRaceImpl object supports RaceChangeListener objects to attach to it. Similarly, Course objects support CourseListeners to observe the course for changes. Currently, our strategy is to serialize the listener graph together with the object graph they observe by declaring the listener interfaces as "extends Serializable." This unfortunately requires all other listener implementations, even the inner anonymous classes used by test cases, to be serializable too. On the other hand this avoids NotSerializableExceptions.
* non-serializable JDK classes: For example, TrackedRaceImpl uses java.util.Timer to delay cache invalidation. In this fortunate case, making the field transient has no negative effects because the next action that would need the cache to be invalidated will set the timer correctly, so no explicit initialization in readObject() is required. There may be other cases, though, where a readObject() implementation is needed to establish some objects in transient fields after deserialization.
* synchronization: serializing a data structure while it is being modified is a bad idea. The java.util.* collection classes are not thread safe. In those cases where concurrent modifications have to be expected, a writeObject() definition is required that synchronizes the serialization with modifying operations. For example, serializing a TrackedRace object together with its GPSFixTracks and WindTracks needs to synchronize with inbound GPSFix / Wind updates.
### Incremental Updates
Many updates to the server's state are small compared to the overall state. For example, adding a GPS fix to a boat's track is a very small change; so is the modification of a leaderboard name or a leaderboard group's description. Such events need to be replicated incrementally. A message queuing system with persistent message queues may be an implementation option that can guarantee delivery also through HTTP connections.
### Implications for the MongoDB Store
A replica may or may not use the same MongoDB store that the master uses. If it uses the same store, replication events must not update the database. This then also holds for the initial load procedure. On the other hand, if the replica uses its own database instance, even the initial load has to update all structures that require persistent storage into the local database.
Using MongoDB's built-in replication requires a robust TCP connection between the MongoDB replicas. This is hard to establish in case only HTTP connections are possible between the replica and the master.
### Use of a Message Queuing System
An MQ system such as ActiveMQ could serve as basis for the implementation of the replication mechanism. We'd have to wrap all commands in messages. To be clarified: what happens when a client subscribes to a topic? Will it receive messages sent to that topic only after it connected? Or will it also receive older messages that have been queued persistently?
### Design
Changes applied to a server that are relevant for replication have to be applied through a single entry point at RacingEventService, using the command pattern provided by the RacingEventServiceOperation interface. This will allow future extensions towards the use of an Operation Transformation (OT) algorithm in case operations can be performed not only on a single master but throughout the replicas. In addition to the course-grained leaderboard / group creations and editing operations, all tracking data recordings need to pass through this interface in order to be replicated properly.
### RacingEventServiceOperations and RacingEventService
Most of the state changes relevant for replication are triggered through the RacingEventService interface. A few changes, such as the recording of GPS and wind data received, are currently immediately updated into internal data structures, such as a DynamicTrackedRace and the track objects it manages, or the Course object owned by a RaceDefinition object. It would be possible to restructure things in such a way that also the tracking data are funneled through the RacingEventService interface.
A RacingEventServiceOperation describes serializable operations that can be applied to a RacingEventService and which can be transmitted to another server instance (a replica) to be applied to another RacingEventService, eventually leading to equal states of the RacingEventService instances.
Question: Where and how do we ensure that all modifications to a RacingEventService that are relevant for replication are actually turned into RacingEventServiceOperations and then reliably serialized to all replicas?
We could let RacingEventServiceImpl manage the creation of the operation objects whenever a changing method is called. This would require distinguishing between an "external" and an "internal" API where the external API methods create the operations which, when applied, use the internal API methods to perform the actual updates.
Or we restrict the externally visible API of RacingEventService to applying RacingEventServiceOperations. Then, all modifications that are relevant for replication have to go through a RacingEventServiceOperation. This is very hard to ensure because internally all APIs, such as those of Leaderboard and LeaderboardGroup and everything are easily accessible.
Answer: We create the operations already in SailingServiceImpl and only call apply on the RacingEventService whose implementation can then notify an optional replicator of the operations that were executed. At this time it would make sense to remove the fine-grained operations which are now only used by the operation implementations from the RacingEventService interface at least. The operation implementations can cast the RacingEventService object to RacingEventServiceImpl and then access the operations again.
### Parallel Processing of Operations
Many types of operations can potentially be processed in parallel. For example, GPS fixes for different tracked races can be inserted into their respective tracks in parallel. So can edit operations for different leaderboards. A thread pool can be used to process the operations. However, each thread in this pool may need to obtain the monitors for the objects it will modify, and the scheduler that sequentially passes the operations to the thread pool for processing needs to wait for the processing thread's acknowledgement that all monitors required have been obtained before triggering the processing of the next operation. The general problem reminds of the versioning graph as created, e.g., by a git repository. Also, Leslie Lamport's vector clocks come to mind.
Some operations are more tricky to parallelize, particularly if we have to assume race conditions during execution. For example, if an operation creates a leaderboard and a follow-up operation adds a column to the leaderboard just created, the leaderboard creation must be complete before the column can be added. Parallelizing such operations is generally dangerous.
Fortunately, the dangerous-to-parallelize operations seem to be the ones that occur with low frequency (leaderboard creation, score corrections, leaderboard column configuration) and have relaxed requirements regarding the lag with which they have to be replicated. We can therefore partition the data and transactions to be replicated into two categories
### Sequential operations
These encompass leaderboard and leaderboard group creation and manipulation, including score corrections. In a replica these operations must be executed in the same sequence they were executed in on the master. It probably encompasses the creation of a tracked race and its course, we well as the updates to a race course with waypoint additions and mark passings because mark passing events relate to the waypoints.
Paralllel operations
These encompass the receiving of raw sensor data. The order in which this data is re-played doesn't matter. The data is time-stamped, and its history is not rewritten (as maybe the case for the mark passings or for the race tracking start/finish times). Applying these operations to a replica is idempotent, so serializing an initial state doesn't need to be synchronized with the receiving of such data. Duplicate application of these operations to a server doesn't do any harm.
# Replicating an SAP Sailing Analytics Server
[[_TOC_]]
### Requirements
Scaling to many (>100) concurrent users following a live sailing race requires providing more than one OSGi server instance. To an extent this is cause by an aspect of our architecture that decides how clients access server data. We use the Google Web Toolkit's (GWT) Remote Procedure Call (RPC) to let the clients request data from the server. These are handled as HTTP POST requests with a servlet composing the response message. This approach makes caching servlet responses to similar requests by a generic HTTP caching / proxy infrastructure difficult.
Using more than one server instance is additionally motivated by the ManyPlayers client architecture. ManyPlayers wants to use our code base but run it on their premises and only be assured that once their instance connects to our "master" instance then all relevant state will be replicated to their instance.
### Server State
The server's state at the current time roughly consists of the following:
* list of races with their tracking data loaded by the server
* a race's state consists of
* RaceDefinition object with name, boat class and event association
* Course object with Waypoints and ControlPoints
* a tracked race's state consisting of
* wind tracks
* raw boat position tracks
* mark position tracks
* mark passings
* race tracking start/finish times
* list of leaderboard groups with their names and descriptions
* list of leaderboards and their association with leaderboard groups
* leaderboard names, columns and the columns' associated tracked races, if any
* discarding rules
* competitor renamings
* explicit score corrections
* disqualification / "max-points" reasons (DSQ, OCS, DNS, DNF, DND, ...)
### Reliability
When a server is configured to be a replica of another "master" server, all updates to the master server must be replicated as quickly as possible to the replica until the replica permanently disconnects from the master server. If the connection breaks temporarily and is restored at a later point in time, all updates that occurred since the connection broke need to be updated to the client.
### Connectivity
Replicated instances may be able to only connect to the master through an HTTP connection. In this case, the master server needs to push updates through an HTTP connection reliably.
### Single Master, Multiple Replicas
For Kiel Week 2012 we want to support a single master server with multiple replicated copies, so-called "replica instances." The replicas are configured for their master node. This configuration doesn't change over the replica's life time. If the master dies, it need to be started again. Replicas then need to recover and re-sync once the new master instance is alive again.
### Concurrency Challenges
Different types of updates to the master server can happen concurrently. Tracking data for multiple races is being received in multiple threads which can execute truly concurrently on a multi-core architecture. Wind data may be received in parallel to receiving other tracking data. Leaderboard and LeaderboardGroup configurations may happen concurrently to all other types of changes. New races may start to get tracked concurrently to the many other server operations. Do we need to ensure that all these events whose effects need to be replicated have to be serialized in an order that is kept the same for all replica to ensure equal resulting states in all replica? What would serializing all these many concurrent events mean to the server's internal concurrency which is important to improve performance?
### Thoughts on Implementation
HTTP push protocols are implemented, e.g., by the Atmosphere framework and by our own, proprietary AbstractHttpPostServlet in the com.sap.sailing.server package. They both use a heartbeat mechanism to re-establish the HTTP connection after it got disconnected. However, both currently don't handle reliable data transmission. If a client is disconnected and re-connects later, it may have missed a number or messages sent while it was disconnected. This would need to be overcome in order to achieve the reliabiliy required for our replication scenario.
### Initial Load
When a replica is first set up and connected to a master server, the master server's current state at some consistent snapshot needs to be replicated to the replica and from then on be kept in sync. How can we produce such a consistent snapshot while understanding which subsequent change events to broadcast to which client?
One approach we're currently testing is using Java Serialization to copy an initial "image" of one server to the next. There are several tricky issues with this approach:
* listeners / observers: several classes offer listeners to be attached; for example, a DynamicTrackedRaceImpl object supports RaceChangeListener objects to attach to it. Similarly, Course objects support CourseListeners to observe the course for changes. Currently, our strategy is to serialize the listener graph together with the object graph they observe by declaring the listener interfaces as "extends Serializable." This unfortunately requires all other listener implementations, even the inner anonymous classes used by test cases, to be serializable too. On the other hand this avoids NotSerializableExceptions.
* non-serializable JDK classes: For example, TrackedRaceImpl uses java.util.Timer to delay cache invalidation. In this fortunate case, making the field transient has no negative effects because the next action that would need the cache to be invalidated will set the timer correctly, so no explicit initialization in readObject() is required. There may be other cases, though, where a readObject() implementation is needed to establish some objects in transient fields after deserialization.
* synchronization: serializing a data structure while it is being modified is a bad idea. The java.util.* collection classes are not thread safe. In those cases where concurrent modifications have to be expected, a writeObject() definition is required that synchronizes the serialization with modifying operations. For example, serializing a TrackedRace object together with its GPSFixTracks and WindTracks needs to synchronize with inbound GPSFix / Wind updates.
### Incremental Updates
Many updates to the server's state are small compared to the overall state. For example, adding a GPS fix to a boat's track is a very small change; so is the modification of a leaderboard name or a leaderboard group's description. Such events need to be replicated incrementally. A message queuing system with persistent message queues may be an implementation option that can guarantee delivery also through HTTP connections.
### Implications for the MongoDB Store
A replica may or may not use the same MongoDB store that the master uses. If it uses the same store, replication events must not update the database. This then also holds for the initial load procedure. On the other hand, if the replica uses its own database instance, even the initial load has to update all structures that require persistent storage into the local database.
Using MongoDB's built-in replication requires a robust TCP connection between the MongoDB replicas. This is hard to establish in case only HTTP connections are possible between the replica and the master.
### Use of a Message Queuing System
An MQ system such as ActiveMQ could serve as basis for the implementation of the replication mechanism. We'd have to wrap all commands in messages. To be clarified: what happens when a client subscribes to a topic? Will it receive messages sent to that topic only after it connected? Or will it also receive older messages that have been queued persistently?
### Design
Changes applied to a server that are relevant for replication have to be applied through a single entry point at RacingEventService, using the command pattern provided by the RacingEventServiceOperation interface. This will allow future extensions towards the use of an Operation Transformation (OT) algorithm in case operations can be performed not only on a single master but throughout the replicas. In addition to the course-grained leaderboard / group creations and editing operations, all tracking data recordings need to pass through this interface in order to be replicated properly.
### RacingEventServiceOperations and RacingEventService
Most of the state changes relevant for replication are triggered through the RacingEventService interface. A few changes, such as the recording of GPS and wind data received, are currently immediately updated into internal data structures, such as a DynamicTrackedRace and the track objects it manages, or the Course object owned by a RaceDefinition object. It would be possible to restructure things in such a way that also the tracking data are funneled through the RacingEventService interface.
A RacingEventServiceOperation describes serializable operations that can be applied to a RacingEventService and which can be transmitted to another server instance (a replica) to be applied to another RacingEventService, eventually leading to equal states of the RacingEventService instances.
Question: Where and how do we ensure that all modifications to a RacingEventService that are relevant for replication are actually turned into RacingEventServiceOperations and then reliably serialized to all replicas?
We could let RacingEventServiceImpl manage the creation of the operation objects whenever a changing method is called. This would require distinguishing between an "external" and an "internal" API where the external API methods create the operations which, when applied, use the internal API methods to perform the actual updates.
Or we restrict the externally visible API of RacingEventService to applying RacingEventServiceOperations. Then, all modifications that are relevant for replication have to go through a RacingEventServiceOperation. This is very hard to ensure because internally all APIs, such as those of Leaderboard and LeaderboardGroup and everything are easily accessible.
Answer: We create the operations already in SailingServiceImpl and only call apply on the RacingEventService whose implementation can then notify an optional replicator of the operations that were executed. At this time it would make sense to remove the fine-grained operations which are now only used by the operation implementations from the RacingEventService interface at least. The operation implementations can cast the RacingEventService object to RacingEventServiceImpl and then access the operations again.
### Parallel Processing of Operations
Many types of operations can potentially be processed in parallel. For example, GPS fixes for different tracked races can be inserted into their respective tracks in parallel. So can edit operations for different leaderboards. A thread pool can be used to process the operations. However, each thread in this pool may need to obtain the monitors for the objects it will modify, and the scheduler that sequentially passes the operations to the thread pool for processing needs to wait for the processing thread's acknowledgement that all monitors required have been obtained before triggering the processing of the next operation. The general problem reminds of the versioning graph as created, e.g., by a git repository. Also, Leslie Lamport's vector clocks come to mind.
Some operations are more tricky to parallelize, particularly if we have to assume race conditions during execution. For example, if an operation creates a leaderboard and a follow-up operation adds a column to the leaderboard just created, the leaderboard creation must be complete before the column can be added. Parallelizing such operations is generally dangerous.
Fortunately, the dangerous-to-parallelize operations seem to be the ones that occur with low frequency (leaderboard creation, score corrections, leaderboard column configuration) and have relaxed requirements regarding the lag with which they have to be replicated. We can therefore partition the data and transactions to be replicated into two categories
### Sequential operations
These encompass leaderboard and leaderboard group creation and manipulation, including score corrections. In a replica these operations must be executed in the same sequence they were executed in on the master. It probably encompasses the creation of a tracked race and its course, we well as the updates to a race course with waypoint additions and mark passings because mark passing events relate to the waypoints.
Paralllel operations
These encompass the receiving of raw sensor data. The order in which this data is re-played doesn't matter. The data is time-stamped, and its history is not rewritten (as maybe the case for the mark passings or for the race tracking start/finish times). Applying these operations to a replica is idempotent, so serializing an initial state doesn't need to be synchronized with the receiving of such data. Duplicate application of these operations to a server doesn't do any harm.
### Replication in SAP Cube
This section will describe, how a working local replica can be set up at the SAP Cube.
The setup at the Travemünder Woche 2014 was done by Steffen Wagner as it follows:
LTE router --> gigabit switch --> cube computers (dhcp) && local replica machine (with e.g. Ubuntu 14.04 LTS & a version of the analytics suite installed)
Steps to get it working:
* The LTE router ( _192.168.1.1_ ) should have DHCP and NS activated. Dhcp was within range _XXX.XXX.1.2 - XXX.XXX.1.49_ and our local replica machine had a static ip address of _XXX.XXX.1.50_. Please make sure, that the ip address is outside of the dhcp range. For a better setup we should think of using the local replica server for dhcp and ns services.
* Setup networking (to your needs) on the local replica machine as follows:
**/etc/networking/interfaces**
<pre>
auto eth0
iface eth0 inet static
address 192.168.1.50
netmask 255.255.255.0
gateway 192.168.1.1
</pre>
**/etc/hosts**
<pre>
127.0.0.1 localhost
127.0.0.1 tw2014 tw2014.sapsailing.com
192.168.1.50 tw2014 tw2014.sapsailing.com
</pre>
* For rewriting the URLs and Ports to the fitting Spectator URL when entering the URL we need a Apache installed (apt-get install apache2) and set up like on our sapsailing.com server. Please check 000-macros.conf, 001-events.conf and 000-main.conf and set it up as you need for the event. They need to be in the config dir of apache (/etc/apache2/mods-enabled and /etc/apache2/sites-enabled)
* For better remote administration of the local replica, set up and autossh tunnel to trac@sapsailing.com and put it into rc.local for autostarting, e.g. as the following:
<pre>
autossh -M 20009 -f -N -L 1337:127.0.0.1:22 -i SSHKEY trac@sapsailing.com
</pre>
* Set up automatic replication in env.sh of the analystics server and start the server (not with root!) with startscript ~/servers/server/start
* On all Cube computers add the folling batch file to the windows task scheduling for a check every minute. The script switches hosts to local replica if available. Before the script can work, you have to edit host file manully and add the required hostnames: e.g. 192.168.1.50 tw2014.sapsailing.com. You also need to replace find and replace to the required ip addresses. Please note that all this would not be needed, if we would use the replica server also for dhcp and ns, so it makes really sense to set this up.
<pre>
@echo off
@setlocal enableextensions enabledelayedexpansion
REM Set a variable for the Windows hosts file location
set "hostpath=%systemroot%\system32\drivers\etc"
set "hostfile=hosts"
REM Make the hosts file writable
attrib -r -s -h "%hostpath%\%hostfile%"
ping -n 1 -w 1000 -l 2000 192.168.1.50
REM if local replica is available
IF %ERRORLEVEL% == 0 (
REM set the string you wish to find
set find=54.229.94.254
REM set the string you wish to replace with
set replace=192.168.1.50
REM if local replica is not available
) ELSE (
REM set ring you wish to find
set find=192.168.1.50
REM set the string you wish to replace with
set replace=54.229.94.254
)
for /f "delims=" %%a in ('type "%hostpath%\%hostfile%"') do (
set "string=%%a"
set "string=!string:%find%=%replace%!"
>> "newfile.txt" echo !string!
)
move /y "newfile.txt" "%hostpath%\%hostfile%"
REM Make the hosts file un-writable - not necessary.
REM attrib +r "%hostpath%\%hostfile%"
echo hostfile done.
</pre>
* If server should be shut down, then first stop replication manually in the backend and then call ~/servers/server/stop. If everything works fine, then the Cube computers should recognize, that the replica machine is not available anymore and then switch to the real sapsailing host.