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
+1022 -2170

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,31 +185,33 @@ 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);
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 && pos != null && heading != null) {
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:
* 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 :)
* 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.
* 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!
* 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."
@@ -224,6 +226,9 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
} else {
result = null;
}
} else {
result = null;
}
return result;
}
@@ -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;
@@ -73,6 +76,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() {
assertNotNull(previousVersion);
@@ -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,56 +31,61 @@ 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> {
TimedPosition pos;
double vrt;
double hrz;
int trn;
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(PathCandidate other) {
if (this.vrt == other.vrt) {
if (Math.abs(this.hrz) == Math.abs(other.hrz)) {
public int compare(PathCandidate p1, PathCandidate p2) {
if (Math.abs(p1.hrz) == Math.abs(p2.hrz)) {
return 0;
} else {
return (Math.abs(this.hrz) < Math.abs(other.hrz) ? -1 : +1);
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 (this.vrt > other.vrt ? -1 : +1);
return (p1.hrz < p2.hrz ? -1 : +1);
}
}
@@ -87,35 +96,56 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
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') {
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 if (nextDirection == 'S') {
tmpBearing = pd.optimalDirectionsUpwind()[1];
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
@@ -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);
// 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());
// 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 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 height-position with reference to apparent wind
Position posHeight = pathPos.getPosition().projectToLineThrough(posEnd, appWind.getBearing());
// 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,10 +499,6 @@ 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);
@@ -283,14 +506,35 @@ public class PathGeneratorTreeGrowWind extends PathGeneratorBase {
//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>();
// 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) {
if ((curPath.vrt > distStartEndMeters)) {
continue;
} else {
//newPathCands = this.getPathCandsBeat(curPath, timeStep, turnLoss, startPos, bearVrt, distStartEndMeters);
newPathCands = this.getPathCandsBeatWind(curPath, timeStep, turnLoss, startPos, endPos, distStartEndMeters);
}
for(PathCandidate 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 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)) {
// 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
@@ -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,27 +75,14 @@ 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();
// diag1 = diag1.reverse();
// southEast = appSouthEast.translateGreatCircle(diag1, diag.scale(tolerance));
Bearing diag2 = appSouthWest.getBearingGreatCircle(appNorthEast);
northEast = appNorthEast.translateGreatCircle(diag2, diag.scale(tolerance));
@@ -93,12 +90,6 @@ public class RectangularBoundary implements Boundary {
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,7 +122,7 @@ 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;
@@ -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 pv;
for (int i = -borderY; i < (vPoints + borderY); i++) {
// System.out.print("nrow "+i+": ");
if (i == 0) {
pv = rcStart;
@@ -188,40 +177,31 @@ 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)+"), ");
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]+", ");
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.)+"), ");
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,44 +209,15 @@ 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;
@@ -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());
}
+66
View File
@@ -91,3 +91,69 @@ These encompass leaderboard and leaderboard group creation and manipulation, inc
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.