mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-14 08:11:00 +00:00
Merge branch 'origin/master'
This commit is contained in:
commit
ab5f3ef1d2
155 files changed
+2805
-1237
No files matched your search
@@ -46,6 +46,7 @@ Like businesses, sailors need the latest information to make strategic decisions
|
||||
* [[Project Planning (bigger development)|wiki/planning]]
|
||||
* [[General Event Planning|wiki/general-event-planning]]
|
||||
* [[Information about Extreme Sailing Series|wiki/extreme-sailing-series]]
|
||||
* [[Kieler Woche 2014 event page|wiki/kielerwoche2014]]
|
||||
|
||||
### Internal services (not related to wiki but useful)
|
||||
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.sap.sailing.domain.common;
|
||||
|
||||
public interface Bounds {
|
||||
Position getNorthEast();
|
||||
Position getSouthWest();
|
||||
Position getNorthWest();
|
||||
Position getSouthEast();
|
||||
|
||||
Bounds intersect(Bounds other);
|
||||
Bounds union(Bounds other);
|
||||
boolean intersects(Bounds other);
|
||||
boolean contains(Position other);
|
||||
boolean contains(Bounds other);
|
||||
boolean isCrossesDateLine();
|
||||
boolean containsLatDeg(double latDeg);
|
||||
boolean containsLngDeg(double lngDeg);
|
||||
boolean isEmpty();
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ public enum DetailType implements Serializable {
|
||||
MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS(1, DESCENDING),
|
||||
TOTAL_DISTANCE_TRAVELED(0, ASCENDING), TOTAL_AVERAGE_SPEED_OVER_GROUND(2, DESCENDING),
|
||||
TOTAL_TIME_SAILED_IN_SECONDS(1, ASCENDING), RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING),
|
||||
RACE_TOTAL_POINTS(2, ASCENDING), REGATTA_TOTAL_POINTS(2, ASCENDING),
|
||||
RACE_TOTAL_POINTS(2, ASCENDING), REGATTA_TOTAL_POINTS(2, ASCENDING), REGATTA_TOTAL_POINTS_SUM(2, ASCENDING),
|
||||
RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL(1, ASCENDING), RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START(1, ASCENDING),
|
||||
RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START(2, DESCENDING),
|
||||
RACE_TIME_TRAVELED(0, ASCENDING), RACE_TIME_TRAVELED_UPWIND(0, ASCENDING), RACE_TIME_TRAVELED_DOWNWIND(0, ASCENDING), RACE_TIME_TRAVELED_REACHING(0, ASCENDING),
|
||||
|
||||
Executable
+166
@@ -0,0 +1,166 @@
|
||||
package com.sap.sailing.domain.common.impl;
|
||||
|
||||
import com.sap.sailing.domain.common.Bounds;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
|
||||
public class BoundsImpl implements Bounds {
|
||||
private final Position sw;
|
||||
private final Position ne;
|
||||
private final boolean crossesDateLine;
|
||||
|
||||
public BoundsImpl(Position sw, Position ne) {
|
||||
super();
|
||||
this.sw = sw;
|
||||
this.ne = ne;
|
||||
crossesDateLine = ne.getLngDeg() < sw.getLngDeg();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getNorthWest() {
|
||||
return new DegreePosition(getNorthEast().getLatDeg(), getSouthWest().getLngDeg());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getSouthEast() {
|
||||
return new DegreePosition(getSouthWest().getLatDeg(), getNorthEast().getLngDeg());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getNorthEast() {
|
||||
return ne;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getSouthWest() {
|
||||
return sw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Considering whether or not this bounds object crosses the date line, calculates the width in degrees longitude
|
||||
* going from <code>west</code> to <code>east</code>. The result is always a non-negative number.
|
||||
*/
|
||||
private double getDistanceDeg(final double west, final double east) {
|
||||
final double diff = east-west;
|
||||
final double result;
|
||||
if (isCrossingDateLine(west, east)) {
|
||||
result = 360+diff; // diff is negative in this case
|
||||
} else {
|
||||
result = diff;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bounds intersect(Bounds other) {
|
||||
final double maxSouthLatDeg = Math.max(getSouthWest().getLatDeg(), other.getSouthWest().getLatDeg());
|
||||
final double minNorthLatDeg = Math.min(getNorthEast().getLatDeg(), other.getNorthEast().getLatDeg());
|
||||
final double westDeg;
|
||||
if (containsLngDeg(other.getSouthWest().getLngDeg())) {
|
||||
westDeg = other.getSouthWest().getLngDeg();
|
||||
} else if (other.containsLngDeg(getSouthWest().getLngDeg())) {
|
||||
westDeg = getSouthWest().getLngDeg();
|
||||
} else {
|
||||
// no intersection
|
||||
westDeg = getSouthWest().getLngDeg();
|
||||
}
|
||||
final double eastDeg;
|
||||
if (containsLngDeg(other.getNorthEast().getLngDeg())) {
|
||||
eastDeg = other.getNorthEast().getLngDeg();
|
||||
} else if (other.containsLngDeg(getNorthEast().getLngDeg())) {
|
||||
eastDeg = getNorthEast().getLngDeg();
|
||||
} else {
|
||||
// no intersection
|
||||
eastDeg = getSouthWest().getLngDeg();
|
||||
}
|
||||
return new BoundsImpl(new DegreePosition(maxSouthLatDeg, westDeg), new DegreePosition(minNorthLatDeg, eastDeg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bounds union(Bounds other) {
|
||||
final double minLatDeg = Math.min(getSouthWest().getLatDeg(), other.getSouthWest().getLatDeg());
|
||||
final double maxLatDeg = Math.max(getNorthEast().getLatDeg(), other.getNorthEast().getLatDeg());
|
||||
// For lng, we can go left or right around the earth; which way results in the smaller bounds? We have four options:
|
||||
final double[] west = new double[] { getSouthWest().getLngDeg(), other.getSouthWest().getLngDeg() };
|
||||
final double[] east = new double[] { getNorthEast().getLngDeg(), other.getNorthEast().getLngDeg() };
|
||||
double minLngDegDistance = Double.MAX_VALUE;
|
||||
double bestWest = 0;
|
||||
double bestEast = 0;
|
||||
for (int w=0; w<2; w++) {
|
||||
for (int e=0; e<2; e++) {
|
||||
double currentLngDegDistance;
|
||||
if (spansLngDeg(west[w], east[e], west[1 - w]) && spansLngDeg(west[w], east[e], east[1 - e])
|
||||
&& (currentLngDegDistance = Math.abs(getDistanceDeg(west[w], east[e]))) < minLngDegDistance) {
|
||||
minLngDegDistance = currentLngDegDistance;
|
||||
bestWest = west[w];
|
||||
bestEast = east[e];
|
||||
}
|
||||
}
|
||||
}
|
||||
return new BoundsImpl(new DegreePosition(minLatDeg, bestWest), new DegreePosition(maxLatDeg, bestEast));
|
||||
}
|
||||
|
||||
private boolean spansLngDeg(double westLngDeg, double eastLngDeg, double lngDeg) {
|
||||
return isCrossingDateLine(westLngDeg, eastLngDeg)
|
||||
? (lngDeg >= westLngDeg && lngDeg <= 180) || (lngDeg >= -180 && lngDeg <= eastLngDeg)
|
||||
: westLngDeg <= lngDeg && lngDeg <= eastLngDeg;
|
||||
}
|
||||
|
||||
private boolean isCrossingDateLine(double westLngDeg, double eastLngDeg) {
|
||||
return westLngDeg > eastLngDeg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersects(Bounds other) {
|
||||
return (containsLatDeg(other.getSouthWest().getLatDeg()) || containsLatDeg(other.getNorthEast().getLatDeg()) ||
|
||||
other.containsLatDeg(getSouthWest().getLatDeg()) || other.containsLatDeg(getNorthEast().getLatDeg()))
|
||||
&& (containsLngDeg(other.getSouthWest().getLngDeg()) || containsLngDeg(other.getNorthEast().getLngDeg()) ||
|
||||
other.containsLngDeg(getSouthWest().getLngDeg()) || other.containsLngDeg(getNorthEast().getLngDeg()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsLatDeg(double latDeg) {
|
||||
return latDeg >= getSouthWest().getLatDeg() && latDeg <= getNorthEast().getLatDeg();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsLngDeg(double lngDeg) {
|
||||
return spansLngDeg(getSouthWest().getLngDeg(), getNorthEast().getLngDeg(), lngDeg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Position other) {
|
||||
return
|
||||
// lat contained:
|
||||
other.getLatDeg() >= getSouthWest().getLatDeg() && other.getLatDeg() <= getNorthEast().getLatDeg() &&
|
||||
// lng contained:
|
||||
(isCrossesDateLine()
|
||||
// between SW and date line...
|
||||
? (other.getLngDeg() <= 180 && other.getLngDeg() >= getSouthWest().getLngDeg()) ||
|
||||
// ...or between date line and NE
|
||||
(other.getLngDeg() >= -180 && other.getLngDeg() <= getNorthEast().getLngDeg())
|
||||
// these bounds are not crossing the date line; simple numeric comparison
|
||||
: other.getLngDeg() >= getSouthWest().getLngDeg() && other.getLngDeg() <= getNorthEast().getLngDeg())
|
||||
;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Bounds other) {
|
||||
return isCrossesDateLine() == other.isCrossesDateLine() && contains(other.getNorthEast()) && contains(other.getSouthWest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCrossesDateLine() {
|
||||
return crossesDateLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return getSouthWest().getLatDeg() >= getNorthEast().getLatDeg() ||
|
||||
getSouthWest().getLngDeg() == getNorthEast().getLngDeg();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{SW: "+getSouthWest().toString()+", NE: "+getNorthEast().toString()+"}";
|
||||
}
|
||||
}
|
||||
+5
@@ -34,4 +34,9 @@ public class FlagPole {
|
||||
this.isDisplayed == otherPole.isDisplayed;
|
||||
}
|
||||
|
||||
public boolean describesSameButForDisplayed(FlagPole otherPole) {
|
||||
return this.upperFlag == otherPole.upperFlag &&
|
||||
this.lowerFlag == otherPole.lowerFlag;
|
||||
}
|
||||
|
||||
}
|
||||
+4
@@ -9,7 +9,9 @@ import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.Timeout;
|
||||
|
||||
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.igtimiadapter.datatypes.AntHrm;
|
||||
@@ -19,6 +21,8 @@ import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualitySatCount;
|
||||
import com.sap.sse.common.Util;
|
||||
|
||||
public class FixFactoryTest {
|
||||
@Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(2 * 60 * 1000);
|
||||
|
||||
private static final String demoJson = "{\n"+
|
||||
"\"EA-AK-AAAG\": {\n"+
|
||||
"\"1\": {\n"+
|
||||
|
||||
+4
@@ -8,7 +8,9 @@ import java.net.URISyntaxException;
|
||||
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.Timeout;
|
||||
|
||||
import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory;
|
||||
import com.sap.sailing.mongodb.MongoDBConfiguration;
|
||||
@@ -16,6 +18,8 @@ import com.sap.sailing.mongodb.MongoDBService;
|
||||
import com.sap.sse.common.Util;
|
||||
|
||||
public class WebSocketServerListTest {
|
||||
@Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(2 * 60 * 1000);
|
||||
|
||||
@Test
|
||||
public void testWebSocketServerList() throws IllegalStateException, ClientProtocolException, IOException, ParseException, URISyntaxException {
|
||||
final ClientImpl testAppClient = new ClientImpl("7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6",
|
||||
|
||||
+4
@@ -10,7 +10,9 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.Timeout;
|
||||
|
||||
import com.sap.sailing.declination.DeclinationService;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
@@ -29,6 +31,8 @@ import com.sap.sailing.domain.igtimiadapter.shared.IgtimiWindReceiver;
|
||||
import com.sap.sailing.domain.tracking.Wind;
|
||||
|
||||
public class WindReceiverTest {
|
||||
@Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(2 * 60 * 1000);
|
||||
|
||||
@Test
|
||||
public void simpleWindReceiverTest() {
|
||||
final List<Wind> windReceived = new ArrayList<>();
|
||||
|
||||
+4
@@ -11,7 +11,9 @@ import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.Timeout;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.sap.sailing.domain.igtimiadapter.Account;
|
||||
@@ -27,6 +29,8 @@ import com.sap.sailing.mongodb.MongoDBService;
|
||||
public class SignInTest {
|
||||
private static final Logger logger = Logger.getLogger(SignInTest.class.getName());
|
||||
|
||||
@Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(2 * 60 * 1000);
|
||||
|
||||
@Test
|
||||
public void testSimpleSignIn() throws ClientProtocolException, IOException, IllegalStateException,
|
||||
ParserConfigurationException, SAXException, ClassNotFoundException, InstantiationException,
|
||||
|
||||
+4
@@ -4,6 +4,8 @@ import java.io.IOException;
|
||||
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.Timeout;
|
||||
|
||||
import com.sap.sailing.domain.igtimiadapter.Account;
|
||||
import com.sap.sailing.domain.igtimiadapter.IgtimiConnection;
|
||||
@@ -13,6 +15,8 @@ import com.sap.sailing.domain.igtimiadapter.impl.Activator;
|
||||
public class AbstractTestWithIgtimiConnection {
|
||||
protected IgtimiConnection connection;
|
||||
|
||||
@Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(2 * 60 * 1000);
|
||||
|
||||
@Before
|
||||
public void setUp() throws ClientProtocolException, IOException, org.json.simple.parser.ParseException {
|
||||
final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory();
|
||||
|
||||
+8
-2
@@ -378,8 +378,14 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
|
||||
if (declinationService == null) {
|
||||
trueHeading = hdgm;
|
||||
} else {
|
||||
Declination declination = declinationService.getDeclination(timePoint, position, /* timeoutForOnlineFetchInMilliseconds 5s */ 5000);
|
||||
trueHeading = hdgm.add(declination.getBearingCorrectedTo(timePoint));
|
||||
try {
|
||||
Declination declination = declinationService.getDeclination(timePoint, position, /* timeoutForOnlineFetchInMilliseconds 5s */ 5000);
|
||||
trueHeading = hdgm.add(declination.getBearingCorrectedTo(timePoint));
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Correction of declination was requested but unsuccessful. Can't correct heading "+
|
||||
hdgm+"@"+timePoint+" by declination. Forwarding exception.");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
trueHeading = null;
|
||||
|
||||
+5
-1
@@ -154,7 +154,11 @@ public class WebSocketConnectionManager extends WebSocketAdapter implements Live
|
||||
|
||||
private void notifyListeners(List<Fix> fixes) {
|
||||
for (BulkFixReceiver listener : listeners.keySet()) {
|
||||
listener.received(fixes);
|
||||
try {
|
||||
listener.received(fixes);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error notifying listener "+listener+" of Igtimi fixes "+fixes, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -1,8 +1,10 @@
|
||||
package com.sap.sailing.domain.persistence;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
@@ -123,4 +125,6 @@ public interface DomainObjectFactory {
|
||||
DomainFactory getBaseDomainFactory();
|
||||
|
||||
Iterable<Entry<DeviceConfigurationMatcher, DeviceConfiguration>> loadAllDeviceConfigurations();
|
||||
|
||||
Map<String, Set<URL>> loadResultUrls();
|
||||
}
|
||||
+5
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.domain.persistence;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URL;
|
||||
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBObject;
|
||||
@@ -131,4 +132,8 @@ public interface MongoObjectFactory {
|
||||
void removeDeviceConfiguration(DeviceConfigurationMatcher matcher);
|
||||
void removeRaceLog(RaceLogIdentifier identifier);
|
||||
|
||||
void storeResultUrl(String resultProviderName, URL url);
|
||||
|
||||
void removeResultUrl(String resultProviderName, URL url);
|
||||
|
||||
}
|
||||
+5
@@ -87,4 +87,9 @@ public enum CollectionNames {
|
||||
* Metadata for the GPSFixes, grouped by Device Identifier.
|
||||
*/
|
||||
GPS_FIXES_METADATA,
|
||||
|
||||
/**
|
||||
* URLs providing race results
|
||||
*/
|
||||
RESULT_URLS,
|
||||
}
|
||||
+30
-11
@@ -1151,10 +1151,10 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
Boolean hasSplitFleetContiguousScoring = (Boolean) dbSeries.get(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name());
|
||||
Boolean firstColumnIsNonDiscardableCarryForward = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name());
|
||||
final BasicDBList dbFleets = (BasicDBList) dbSeries.get(FieldNames.SERIES_FLEETS.name());
|
||||
Map<String, Fleet> fleetsByName = loadFleets(dbFleets);
|
||||
List<Fleet> fleets = loadFleets(dbFleets);
|
||||
BasicDBList dbRaceColumns = (BasicDBList) dbSeries.get(FieldNames.SERIES_RACE_COLUMNS.name());
|
||||
Iterable<String> raceColumnNames = loadRaceColumnNames(dbRaceColumns, fleetsByName);
|
||||
Series series = new SeriesImpl(name, isMedal, fleetsByName.values(), raceColumnNames, trackedRegattaRegistry);
|
||||
Iterable<String> raceColumnNames = loadRaceColumnNames(dbRaceColumns);
|
||||
Series series = new SeriesImpl(name, isMedal, fleets, raceColumnNames, trackedRegattaRegistry);
|
||||
if (dbSeries.get(FieldNames.SERIES_DISCARDING_THRESHOLDS.name()) != null) {
|
||||
ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbSeries, FieldNames.SERIES_DISCARDING_THRESHOLDS);
|
||||
series.setResultDiscardingRule(resultDiscardingRule);
|
||||
@@ -1172,11 +1172,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
return series;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fleetsByName used to ensure the {@link RaceColumn#getFleets()} points to the same {@link Fleet} objects also
|
||||
* used in the {@link Series#getFleets()} collection.
|
||||
*/
|
||||
private Iterable<String> loadRaceColumnNames(BasicDBList dbRaceColumns, Map<String, Fleet> fleetsByName) {
|
||||
private Iterable<String> loadRaceColumnNames(BasicDBList dbRaceColumns) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (Object o : dbRaceColumns) {
|
||||
DBObject dbRaceColumn = (DBObject) o;
|
||||
@@ -1201,12 +1197,12 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Fleet> loadFleets(BasicDBList dbFleets) {
|
||||
Map<String, Fleet> result = new HashMap<String, Fleet>();
|
||||
private List<Fleet> loadFleets(BasicDBList dbFleets) {
|
||||
List<Fleet> result = new ArrayList<Fleet>();
|
||||
for (Object o : dbFleets) {
|
||||
DBObject dbFleet = (DBObject) o;
|
||||
Fleet fleet = loadFleet(dbFleet);
|
||||
result.put(fleet.getName(), fleet);
|
||||
result.add(fleet);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1760,4 +1756,27 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
|
||||
stringRepresentation, deviceType, stringRepresentation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Set<URL>> loadResultUrls() {
|
||||
Map<String, Set<URL>> resultUrls = new HashMap<>();
|
||||
DBCollection resultUrlCollection = database.getCollection(CollectionNames.RESULT_URLS.name());
|
||||
for (DBObject dbObject : resultUrlCollection.find()) {
|
||||
String providerName = (String) dbObject.get(FieldNames.RESULT_PROVIDERNAME.name());
|
||||
String urlString = (String) dbObject.get(FieldNames.RESULT_URL.name());
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(urlString);
|
||||
} catch (MalformedURLException e) {
|
||||
logger.log(Level.SEVERE, "Failed to parse result Url String: " + urlString + ". Did not load url!");
|
||||
continue;
|
||||
}
|
||||
if (!resultUrls.containsKey(providerName)) {
|
||||
resultUrls.put(providerName, new HashSet<URL>());
|
||||
}
|
||||
Set<URL> set = resultUrls.get(providerName);
|
||||
set.add(url);
|
||||
}
|
||||
return resultUrls;
|
||||
}
|
||||
}
|
||||
+3
@@ -108,4 +108,7 @@ public enum FieldNames {
|
||||
|
||||
// Timespan
|
||||
FROM_MILLIS, TO_MILLIS,
|
||||
|
||||
// Result Import url fields
|
||||
RESULT_PROVIDERNAME, RESULT_URL
|
||||
}
|
||||
+17
@@ -1225,4 +1225,21 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
storeRaceLogIdentifier(identifier, query);
|
||||
getRaceLogCollection().remove(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeResultUrl(String resultProviderName, URL url) {
|
||||
DBCollection resultUrlsCollection = database.getCollection(CollectionNames.RESULT_URLS.name());
|
||||
DBObject query = new BasicDBObject(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName);
|
||||
DBObject entry = new BasicDBObject(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName);
|
||||
entry.put(FieldNames.RESULT_URL.name(), url.toString());
|
||||
resultUrlsCollection.update(query, entry, /* upsrt */true, /* multi */false, WriteConcern.SAFE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeResultUrl(String resultProviderName, URL url) {
|
||||
DBCollection resultUrlsCollection = database.getCollection(CollectionNames.RESULT_URLS.name());
|
||||
DBObject query = new BasicDBObjectBuilder().add(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName)
|
||||
.add(FieldNames.RESULT_URL.name(), url.toString()).get();
|
||||
resultUrlsCollection.remove(query);
|
||||
}
|
||||
}
|
||||
+11
-14
@@ -1,7 +1,6 @@
|
||||
package com.sap.sailing.domain.racelogtracking.test.impl;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -31,7 +30,7 @@ import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifie
|
||||
import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegatta;
|
||||
import com.sap.sailing.domain.tracking.impl.BaseRaceChangeListener;
|
||||
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
|
||||
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
||||
import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl;
|
||||
@@ -39,7 +38,8 @@ import com.sap.sailing.domain.tracking.impl.TrackedRegattaImpl;
|
||||
public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
|
||||
private final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("49er");
|
||||
|
||||
private boolean finishedLoadingFirstBeforeSecond = true;
|
||||
private int numberOfSwitchesBetweenLoadingRaceLogs = 0;
|
||||
private Integer previouslyLoading = null;
|
||||
private int numFixesReceived = 0;
|
||||
|
||||
@Test
|
||||
@@ -114,19 +114,16 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
|
||||
DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race, Collections.<Sideline>emptyList(),
|
||||
EmptyWindStore.INSTANCE, store, 0, 0, 0);
|
||||
|
||||
trackedRace.addListener(new BaseRaceChangeListener() {
|
||||
boolean currentlyLoadingFirst = true;
|
||||
trackedRace.addListener(new AbstractRaceChangeListener() {
|
||||
@Override
|
||||
public void competitorPositionChanged(GPSFixMoving fix, Competitor competitor) {
|
||||
numFixesReceived++;
|
||||
if (currentlyLoadingFirst) {
|
||||
if (fix.getTimePoint().asMillis() > numFixes/2) {
|
||||
currentlyLoadingFirst = false;
|
||||
}
|
||||
} else {
|
||||
if (fix.getTimePoint().asMillis() <= numFixes/2) {
|
||||
finishedLoadingFirstBeforeSecond = false;
|
||||
}
|
||||
int currentlyLoading = fix.getTimePoint().asMillis() <= numFixes/2 ? 1 : 2;
|
||||
if (previouslyLoading == null) {
|
||||
previouslyLoading = currentlyLoading;
|
||||
} else if (previouslyLoading != currentlyLoading) {
|
||||
previouslyLoading = currentlyLoading;
|
||||
numberOfSwitchesBetweenLoadingRaceLogs++;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -137,7 +134,7 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
|
||||
trackedRace.waitForLoadingFromGPSFixStoreToFinishRunning(raceLog);
|
||||
trackedRace.waitForLoadingFromGPSFixStoreToFinishRunning(raceLog2);
|
||||
|
||||
assertTrue(finishedLoadingFirstBeforeSecond);
|
||||
assertEquals(1, numberOfSwitchesBetweenLoadingRaceLogs);
|
||||
assertEquals(numFixes, numFixesReceived);
|
||||
}
|
||||
}
|
||||
+3
@@ -124,6 +124,9 @@ public class SharedDomainFactoryImpl implements SharedDomainFactory {
|
||||
|
||||
@Override
|
||||
public Nationality getOrCreateNationality(String threeLetterIOCCode) {
|
||||
if (threeLetterIOCCode == null) {
|
||||
threeLetterIOCCode = " ";
|
||||
}
|
||||
synchronized (nationalityCache) {
|
||||
Nationality result = nationalityCache.get(threeLetterIOCCode);
|
||||
if (result == null) {
|
||||
|
||||
+9
-1
@@ -1,17 +1,25 @@
|
||||
package com.sap.sailing.domain.racelog.analyzing.impl;
|
||||
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.racelog.Flags;
|
||||
import com.sap.sailing.domain.racelog.RaceLog;
|
||||
import com.sap.sailing.domain.racelog.RaceLogFlagEvent;
|
||||
|
||||
public class IndividualRecallDisplayedFinder extends IndividualRecallFinder {
|
||||
private final TimePoint at;
|
||||
|
||||
public IndividualRecallDisplayedFinder(RaceLog raceLog) {
|
||||
this(raceLog, null);
|
||||
}
|
||||
|
||||
public IndividualRecallDisplayedFinder(RaceLog raceLog, TimePoint at) {
|
||||
super(raceLog);
|
||||
this.at = at;
|
||||
}
|
||||
|
||||
protected boolean isRelevant(RaceLogFlagEvent flagEvent) {
|
||||
return flagEvent.getUpperFlag().equals(Flags.XRAY) && flagEvent.isDisplayed();
|
||||
boolean disregardBecauseAfterAt = at != null && flagEvent.getTimePoint().after(at);
|
||||
return ! disregardBecauseAfterAt && flagEvent.getUpperFlag().equals(Flags.XRAY) && flagEvent.isDisplayed();
|
||||
}
|
||||
|
||||
}
|
||||
+10
-1
@@ -1,17 +1,26 @@
|
||||
package com.sap.sailing.domain.racelog.analyzing.impl;
|
||||
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.racelog.Flags;
|
||||
import com.sap.sailing.domain.racelog.RaceLog;
|
||||
import com.sap.sailing.domain.racelog.RaceLogFlagEvent;
|
||||
|
||||
public class IndividualRecallRemovedFinder extends IndividualRecallFinder {
|
||||
|
||||
private final TimePoint at;
|
||||
|
||||
public IndividualRecallRemovedFinder(RaceLog raceLog) {
|
||||
this(raceLog, null);
|
||||
}
|
||||
|
||||
public IndividualRecallRemovedFinder(RaceLog raceLog, TimePoint at) {
|
||||
super(raceLog);
|
||||
this.at = at;
|
||||
}
|
||||
|
||||
protected boolean isRelevant(RaceLogFlagEvent flagEvent) {
|
||||
return flagEvent.getUpperFlag().equals(Flags.XRAY) && !flagEvent.isDisplayed();
|
||||
boolean disregardBecauseAfterAt = at != null && flagEvent.getTimePoint().after(at);
|
||||
return ! disregardBecauseAfterAt && flagEvent.getUpperFlag().equals(Flags.XRAY) && !flagEvent.isDisplayed();
|
||||
}
|
||||
|
||||
}
|
||||
+6
-2
@@ -9,11 +9,15 @@ public class IsIndividualRecallDisplayedAnalyzer extends RaceLogAnalyzer<Boolean
|
||||
private final IndividualRecallFinder removedFinder;
|
||||
|
||||
public IsIndividualRecallDisplayedAnalyzer(RaceLog raceLog) {
|
||||
this(raceLog, new IndividualRecallDisplayedFinder(raceLog), new IndividualRecallRemovedFinder(raceLog));
|
||||
this(raceLog, null);
|
||||
}
|
||||
|
||||
public IsIndividualRecallDisplayedAnalyzer(RaceLog raceLog, TimePoint at) {
|
||||
this(raceLog, new IndividualRecallDisplayedFinder(raceLog, at), new IndividualRecallRemovedFinder(raceLog, at), at);
|
||||
}
|
||||
|
||||
public IsIndividualRecallDisplayedAnalyzer(RaceLog raceLog, IndividualRecallDisplayedFinder displayedFinder,
|
||||
IndividualRecallRemovedFinder removedFinder) {
|
||||
IndividualRecallRemovedFinder removedFinder, TimePoint at) {
|
||||
super(raceLog);
|
||||
this.displayedFinder = displayedFinder;
|
||||
this.removedFinder = removedFinder;
|
||||
|
||||
+93
-21
@@ -8,25 +8,29 @@ import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.racelog.FlagPole;
|
||||
|
||||
/**
|
||||
* Allows you to query the current and state of displayed and remove flags.
|
||||
* Allows you to query the current state of displayed and removed flags.
|
||||
*/
|
||||
public class FlagPoleState {
|
||||
|
||||
private final List<FlagPole> currentState;
|
||||
private final List<FlagPole> nextState;
|
||||
private final TimePoint currentStateValidFrom;
|
||||
private final TimePoint nextStateValidFrom;
|
||||
private final boolean hasNextState;
|
||||
|
||||
public FlagPoleState(List<FlagPole> currentState) {
|
||||
this(currentState, null, null, false);
|
||||
public FlagPoleState(List<FlagPole> currentState, TimePoint currentStateValidFrom) {
|
||||
this(currentState, currentStateValidFrom, null, null, false);
|
||||
}
|
||||
|
||||
public FlagPoleState(List<FlagPole> currentState, List<FlagPole> nextState, TimePoint nextStateValidFrom) {
|
||||
this(currentState, nextState, nextStateValidFrom, true);
|
||||
public FlagPoleState(List<FlagPole> currentState, TimePoint currentStateValidFrom,
|
||||
List<FlagPole> nextState, TimePoint nextStateValidFrom) {
|
||||
this(currentState, currentStateValidFrom, nextState, nextStateValidFrom, true);
|
||||
}
|
||||
|
||||
private FlagPoleState(List<FlagPole> currentState, List<FlagPole> nextState, TimePoint nextStateValidFrom, boolean hasNextState) {
|
||||
private FlagPoleState(List<FlagPole> currentState, TimePoint currentStateValidFrom,
|
||||
List<FlagPole> nextState, TimePoint nextStateValidFrom, boolean hasNextState) {
|
||||
this.currentState = currentState;
|
||||
this.currentStateValidFrom = currentStateValidFrom;
|
||||
this.nextState = nextState;
|
||||
this.nextStateValidFrom = nextStateValidFrom;
|
||||
this.hasNextState = hasNextState;
|
||||
@@ -42,30 +46,51 @@ public class FlagPoleState {
|
||||
public TimePoint getNextStateValidFrom() {
|
||||
return nextStateValidFrom;
|
||||
}
|
||||
|
||||
public TimePoint getCurrentStateValidFrom() {
|
||||
return currentStateValidFrom;
|
||||
}
|
||||
|
||||
public boolean hasNextState() {
|
||||
return hasNextState;
|
||||
}
|
||||
|
||||
public static List<FlagPole> computeChanges(List<FlagPole> before, List<FlagPole> after) {
|
||||
before = before == null ? Collections.<FlagPole>emptyList() : before;
|
||||
after = after == null ? Collections.<FlagPole>emptyList() : after;
|
||||
|
||||
List<FlagPole> beforeWithoutMatchInAfter = new ArrayList<FlagPole>(before);
|
||||
List<FlagPole> changes = new ArrayList<FlagPole>();
|
||||
for (FlagPole poleAfter : after) {
|
||||
boolean hasSamePoleBefore = false;
|
||||
for (FlagPole poleBefore : before) {
|
||||
if (poleAfter.describesSameButForDisplayed(poleBefore)) {
|
||||
beforeWithoutMatchInAfter.remove(poleBefore);
|
||||
}
|
||||
if (poleAfter.describesSame(poleBefore)) {
|
||||
hasSamePoleBefore = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasSamePoleBefore) {
|
||||
changes.add(poleAfter);
|
||||
}
|
||||
}
|
||||
|
||||
for (FlagPole poleBefore : beforeWithoutMatchInAfter) {
|
||||
if (poleBefore.isDisplayed()) {
|
||||
changes.add(new FlagPole(poleBefore.getUpperFlag(), poleBefore.getLowerFlag(), false));
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
public List<FlagPole> computeUpcomingChanges() {
|
||||
if (!hasNextState) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<FlagPole> changes = new ArrayList<FlagPole>();
|
||||
for (FlagPole nextPole : nextState) {
|
||||
boolean hasSamePoleInCurrentState = false;
|
||||
for (FlagPole currentPole : currentState) {
|
||||
if (nextPole.describesSame(currentPole)) {
|
||||
hasSamePoleInCurrentState = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasSamePoleInCurrentState) {
|
||||
changes.add(nextPole);
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
return computeChanges(currentState, nextState);
|
||||
}
|
||||
|
||||
public static boolean describesSameState(FlagPoleState left, FlagPoleState right) {
|
||||
@@ -99,5 +124,52 @@ public class FlagPoleState {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public FlagPoleState getPreviousState(ReadonlyRacingProcedure racingProcedure, TimePoint startTime) {
|
||||
if (currentStateValidFrom == null) {
|
||||
return this;
|
||||
}
|
||||
return racingProcedure.getActiveFlags(startTime, currentStateValidFrom.minus(1));
|
||||
}
|
||||
|
||||
public static FlagPole getMostInterestingFlagPole(List<FlagPole> poles) {
|
||||
return getMostInterestingFlagPole(poles, poles);
|
||||
}
|
||||
|
||||
public static FlagPole getMostInterestingFlagPole(List<FlagPole> previousPoles, List<FlagPole> currentPoles) {
|
||||
List<FlagPole> changes = computeChanges(previousPoles, currentPoles);
|
||||
FlagPole poleRemoved = null;
|
||||
for (FlagPole changedPole : changes) {
|
||||
if (changedPole.isDisplayed()) {
|
||||
return changedPole;
|
||||
} else {
|
||||
poleRemoved = changedPole;
|
||||
}
|
||||
}
|
||||
if (poleRemoved != null) {
|
||||
return poleRemoved;
|
||||
}
|
||||
|
||||
for (FlagPole currentPole : currentPoles) {
|
||||
if (currentPole.isDisplayed()) {
|
||||
return currentPole;
|
||||
}
|
||||
}
|
||||
return currentPoles.size() == 0 ? null : currentPoles.get(0);
|
||||
}
|
||||
|
||||
public boolean hasPoleChanged(FlagPole newPole) {
|
||||
boolean found = false;
|
||||
for (FlagPole current : currentState) {
|
||||
if (current.describesSameButForDisplayed(newPole)) {
|
||||
found = true;
|
||||
if (current.isDisplayed() != newPole.isDisplayed()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! found && newPole.isDisplayed()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+10
@@ -68,6 +68,16 @@ public interface ReadonlyRacingProcedure extends RaceStateEventProcessor, RaceSt
|
||||
*/
|
||||
boolean isIndividualRecallDisplayed();
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if there is an individual recall signaled in the {@link RaceLog} at the TimePoint {@code at}.
|
||||
*/
|
||||
boolean isIndividualRecallDisplayed(TimePoint at);
|
||||
|
||||
/**
|
||||
* Gets the time the individual recall flag was displayed (or <code>null</code>).
|
||||
*/
|
||||
TimePoint getIndividualRecallDisplayedTime();
|
||||
|
||||
/**
|
||||
* Gets the time the individual recall flag was removed (or <code>null</code>).
|
||||
*/
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ public class BasicRacingProcedureImpl extends BaseRacingProcedure {
|
||||
|
||||
@Override
|
||||
public FlagPoleState getActiveFlags(TimePoint startTime, TimePoint now) {
|
||||
return new FlagPoleState(Collections.<FlagPole>emptyList());
|
||||
return new FlagPoleState(Collections.<FlagPole>emptyList(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+13
-7
@@ -91,37 +91,43 @@ public class ESSRacingProcedureImpl extends BaseRacingProcedure implements ESSRa
|
||||
public FlagPoleState getActiveFlags(TimePoint startTime, TimePoint now) {
|
||||
if (now.before(startTime.minus(startPhaseAPDownInterval))) {
|
||||
return new FlagPoleState(
|
||||
Arrays.asList(new FlagPole(Flags.AP, true), new FlagPole(Flags.ESSTHREE, false)),
|
||||
Arrays.asList(new FlagPole(Flags.AP, true), new FlagPole(Flags.ESSTHREE, false)), null,
|
||||
Arrays.asList(new FlagPole(Flags.AP, false), new FlagPole(Flags.ESSTHREE, false)),
|
||||
startTime.minus(startPhaseAPDownInterval));
|
||||
} else if (now.before(startTime.minus(startPhaseESSThreeUpInterval))) {
|
||||
return new FlagPoleState(
|
||||
Arrays.asList(new FlagPole(Flags.AP, false), new FlagPole(Flags.ESSTHREE, false)),
|
||||
Arrays.asList(new FlagPole(Flags.AP, false), new FlagPole(Flags.ESSTHREE, false)),
|
||||
startTime.minus(startPhaseAPDownInterval),
|
||||
Arrays.asList(new FlagPole(Flags.ESSTHREE, true), new FlagPole(Flags.ESSTWO, false)),
|
||||
startTime.minus(startPhaseESSThreeUpInterval));
|
||||
} else if (now.before(startTime.minus(startPhaseESSTwoUpInterval))) {
|
||||
return new FlagPoleState(
|
||||
Arrays.asList(new FlagPole(Flags.ESSTHREE, true), new FlagPole(Flags.ESSTWO, false)),
|
||||
Arrays.asList(new FlagPole(Flags.ESSTHREE, true), new FlagPole(Flags.ESSTWO, false)),
|
||||
startTime.minus(startPhaseESSThreeUpInterval),
|
||||
Arrays.asList(new FlagPole(Flags.ESSTWO, true), new FlagPole(Flags.ESSONE, false)),
|
||||
startTime.minus(startPhaseESSTwoUpInterval));
|
||||
} else if (now.before(startTime.minus(startPhaseESSOneUpInterval))) {
|
||||
return new FlagPoleState(
|
||||
Arrays.asList(new FlagPole(Flags.ESSTWO, true), new FlagPole(Flags.ESSONE, false)),
|
||||
Arrays.asList(new FlagPole(Flags.ESSTWO, true), new FlagPole(Flags.ESSONE, false)),
|
||||
startTime.minus(startPhaseESSTwoUpInterval),
|
||||
Arrays.asList(new FlagPole(Flags.ESSONE, true)),
|
||||
startTime.minus(startPhaseESSOneUpInterval));
|
||||
} if (now.before(startTime)) {
|
||||
return new FlagPoleState(
|
||||
Arrays.asList(new FlagPole(Flags.ESSONE, true)),
|
||||
Arrays.asList(new FlagPole(Flags.ESSONE, true)),
|
||||
startTime.minus(startPhaseESSOneUpInterval),
|
||||
Arrays.asList(new FlagPole(Flags.ESSONE, false)),
|
||||
startTime);
|
||||
} else {
|
||||
if (isIndividualRecallDisplayed()) {
|
||||
if (isIndividualRecallDisplayed(now)) {
|
||||
return new FlagPoleState(
|
||||
Arrays.asList(new FlagPole(Flags.XRAY, true)),
|
||||
getIndividualRecallDisplayedTime(),
|
||||
Arrays.asList(new FlagPole(Flags.XRAY, false)),
|
||||
getIndividualRecallRemovalTime());
|
||||
} else {
|
||||
return new FlagPoleState(Collections.<FlagPole>emptyList());
|
||||
TimePoint recallRemoved = getIndividualRecallRemovalTime();
|
||||
return new FlagPoleState(Collections.<FlagPole>emptyList(), recallRemoved == null ? startTime : recallRemoved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-7
@@ -146,24 +146,27 @@ public class GateStartRacingProcedureImpl extends BaseRacingProcedure implements
|
||||
TimePoint gateShutdownTime = getGateShutdownTimePoint(startTime);
|
||||
if (now.before(startTime.minus(startPhaseClassOverGolfUpIntervall))) {
|
||||
return new FlagPoleState(Arrays.asList(new FlagPole(classFlag, Flags.GOLF, false), new FlagPole(Flags.PAPA,
|
||||
false)), Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, false)),
|
||||
false)), null, Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, false)),
|
||||
startTime.minus(startPhaseClassOverGolfUpIntervall));
|
||||
} else if (now.before(startTime.minus(startPhasePapaUpInterval))) {
|
||||
return new FlagPoleState(Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA,
|
||||
false)), Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, true)),
|
||||
false)), startTime.minus(startPhaseClassOverGolfUpIntervall),
|
||||
Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, true)),
|
||||
startTime.minus(startPhasePapaUpInterval));
|
||||
} else if (now.before(startTime.minus(startPhasePapaDownInterval))) {
|
||||
return new FlagPoleState(Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA,
|
||||
true)), Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, false)),
|
||||
true)), startTime.minus(startPhasePapaUpInterval),
|
||||
Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, false)),
|
||||
startTime.minus(startPhasePapaDownInterval));
|
||||
} else if (now.before(startTime)) {
|
||||
return new FlagPoleState(Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA,
|
||||
false)), Arrays.asList(new FlagPole(classFlag, false), new FlagPole(Flags.GOLF, true)), startTime);
|
||||
false)), startTime.minus(startPhasePapaDownInterval),
|
||||
Arrays.asList(new FlagPole(classFlag, false), new FlagPole(Flags.GOLF, true)), startTime);
|
||||
} else if (now.before(gateShutdownTime)) {
|
||||
return new FlagPoleState(Arrays.asList(new FlagPole(Flags.GOLF, true)), Arrays.asList(new FlagPole(
|
||||
Flags.GOLF, false)), gateShutdownTime);
|
||||
return new FlagPoleState(Arrays.asList(new FlagPole(Flags.GOLF, true)), startTime,
|
||||
Arrays.asList(new FlagPole(Flags.GOLF, false)), gateShutdownTime);
|
||||
} else {
|
||||
return new FlagPoleState(Arrays.asList(new FlagPole(Flags.GOLF, false)));
|
||||
return new FlagPoleState(Arrays.asList(new FlagPole(Flags.GOLF, false)), gateShutdownTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+32
-8
@@ -13,6 +13,7 @@ import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
|
||||
import com.sap.sailing.domain.racelog.RaceLogEventFactory;
|
||||
import com.sap.sailing.domain.racelog.RaceLogEventVisitor;
|
||||
import com.sap.sailing.domain.racelog.analyzing.impl.IndividualRecallDisplayedFinder;
|
||||
import com.sap.sailing.domain.racelog.analyzing.impl.IndividualRecallRemovedFinder;
|
||||
import com.sap.sailing.domain.racelog.analyzing.impl.IsIndividualRecallDisplayedAnalyzer;
|
||||
import com.sap.sailing.domain.racelog.impl.RaceLogChangedVisitor;
|
||||
import com.sap.sailing.domain.racelog.state.RaceStateEvent;
|
||||
@@ -46,6 +47,7 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
|
||||
private final RacingProcedureChangedListeners<? extends RacingProcedureChangedListener> changedListeners;
|
||||
private final IsIndividualRecallDisplayedAnalyzer isRecallDisplayedAnalyzer;
|
||||
private final IndividualRecallDisplayedFinder recallDisplayedFinder;
|
||||
private final IndividualRecallRemovedFinder recallRemovedFinder;
|
||||
private final RaceLogEventVisitor raceLogListener;
|
||||
|
||||
private boolean cachedIsIndividualRecallDisplayed;
|
||||
@@ -67,6 +69,7 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
|
||||
this.changedListeners = createChangedListenerContainer();
|
||||
this.isRecallDisplayedAnalyzer = new IsIndividualRecallDisplayedAnalyzer(raceLog);
|
||||
this.recallDisplayedFinder = new IndividualRecallDisplayedFinder(raceLog);
|
||||
this.recallRemovedFinder = new IndividualRecallRemovedFinder(raceLog);
|
||||
|
||||
this.raceLogListener = new RaceLogChangedVisitor(this);
|
||||
this.raceLog.addListener(raceLogListener);
|
||||
@@ -126,17 +129,34 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
|
||||
public boolean isIndividualRecallDisplayed() {
|
||||
return cachedIsIndividualRecallDisplayed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIndividualRecallDisplayed(TimePoint at) {
|
||||
if (hasIndividualRecall()) {
|
||||
return new IsIndividualRecallDisplayedAnalyzer(getRaceLog(), at).analyze();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimePoint getIndividualRecallDisplayedTime() {
|
||||
if (hasIndividualRecall()) {
|
||||
return recallDisplayedFinder.analyze();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimePoint getIndividualRecallRemovalTime() {
|
||||
TimePoint displayed = getIndividualRecallDisplayedTime();
|
||||
if (hasIndividualRecall()) {
|
||||
if (isIndividualRecallDisplayed()) {
|
||||
TimePoint displayTime = recallDisplayedFinder.analyze();
|
||||
if (displayTime != null) {
|
||||
return displayTime.plus(individualRecallRemovalTimeout);
|
||||
}
|
||||
TimePoint removedEvent = recallRemovedFinder.analyze();
|
||||
if (removedEvent != null && (displayed == null || removedEvent.after(displayed))) {
|
||||
return removedEvent;
|
||||
}
|
||||
|
||||
}
|
||||
if (displayed != null) {
|
||||
return displayed.plus(individualRecallRemovalTimeout);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -177,18 +197,22 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
|
||||
|
||||
protected void update() {
|
||||
boolean isRecallDisplayed = isRecallDisplayedAnalyzer.analyze();
|
||||
|
||||
if (cachedIsIndividualRecallDisplayed != isRecallDisplayed) {
|
||||
cachedIsIndividualRecallDisplayed = isRecallDisplayed;
|
||||
if (cachedIsIndividualRecallDisplayed) {
|
||||
changedListeners.onIndividualRecallDisplayed(this);
|
||||
changedListeners.onActiveFlagsChanged(this);
|
||||
rescheduleIndividualRecallTimeout(recallDisplayedFinder.analyze());
|
||||
} else {
|
||||
changedListeners.onIndividualRecallRemoved(this);
|
||||
changedListeners.onActiveFlagsChanged(this);
|
||||
unscheduleStateEvent(RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
// always call listeners for changed flag, as this does not only affect recal, but also
|
||||
// changes start procedure, for which some text elements need to be updated
|
||||
// ({@link BaseRaceInfoFragmen#renderFlagChangesCountdown}
|
||||
changedListeners.onActiveFlagsChanged(this);
|
||||
}
|
||||
|
||||
private void rescheduleIndividualRecallTimeout(TimePoint displayTime) {
|
||||
|
||||
+12
-6
@@ -126,32 +126,38 @@ public class RRS26RacingProcedureImpl extends BaseRacingProcedure implements RRS
|
||||
getConfiguration().getClassFlag() : Flags.CLASS;
|
||||
if (now.before(startTime.minus(startPhaseClassUpInterval))) {
|
||||
return new FlagPoleState(
|
||||
Arrays.asList(new FlagPole(classFlag, false), new FlagPole(cachedStartmodeFlag, false)),
|
||||
Arrays.asList(new FlagPole(classFlag, false), new FlagPole(cachedStartmodeFlag, false)),
|
||||
null,
|
||||
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)),
|
||||
startTime.minus(startPhaseClassUpInterval));
|
||||
} else if (now.before(startTime.minus(startPhaseStartModeUpInterval))) {
|
||||
return new FlagPoleState(
|
||||
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)),
|
||||
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)),
|
||||
startTime.minus(startPhaseClassUpInterval),
|
||||
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, true)),
|
||||
startTime.minus(startPhaseStartModeUpInterval));
|
||||
} else if (now.before(startTime.minus(startPhaseStartModeDownInterval))) {
|
||||
return new FlagPoleState(
|
||||
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, true)),
|
||||
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, true)),
|
||||
startTime.minus(startPhaseStartModeUpInterval),
|
||||
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)),
|
||||
startTime.minus(startPhaseStartModeDownInterval));
|
||||
} else if (now.before(startTime)) {
|
||||
return new FlagPoleState(
|
||||
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)),
|
||||
Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)),
|
||||
startTime.minus(startPhaseStartModeDownInterval),
|
||||
Arrays.asList(new FlagPole(classFlag, false), new FlagPole(cachedStartmodeFlag, false)),
|
||||
startTime);
|
||||
} else {
|
||||
if (isIndividualRecallDisplayed()) {
|
||||
if (isIndividualRecallDisplayed(now)) {
|
||||
return new FlagPoleState(
|
||||
Arrays.asList(new FlagPole(Flags.XRAY, true)),
|
||||
getIndividualRecallDisplayedTime(),
|
||||
Arrays.asList(new FlagPole(Flags.XRAY, false)),
|
||||
getIndividualRecallRemovalTime());
|
||||
} else {
|
||||
return new FlagPoleState(Collections.<FlagPole>emptyList());
|
||||
TimePoint recallRemoved = getIndividualRecallRemovalTime();
|
||||
return new FlagPoleState(Collections.<FlagPole>emptyList(), recallRemoved == null ? startTime : recallRemoved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.common.Bounds;
|
||||
import com.sap.sailing.domain.common.impl.BoundsImpl;
|
||||
import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
|
||||
public class BoundsTest {
|
||||
@Test
|
||||
public void simpleContains() {
|
||||
Bounds b = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(3, 3));
|
||||
assertTrue(b.contains(new DegreePosition(2, 2)));
|
||||
assertFalse(b.contains(new DegreePosition(4, 4)));
|
||||
assertFalse(b.contains(new DegreePosition(0, 0)));
|
||||
assertFalse(b.contains(new DegreePosition(0, 2)));
|
||||
assertFalse(b.contains(new DegreePosition(2, 0)));
|
||||
assertFalse(b.contains(new DegreePosition(2, 4)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void crossDateLineContains() {
|
||||
Bounds b = new BoundsImpl(new DegreePosition(1, 179), new DegreePosition(3, -179));
|
||||
assertTrue(b.contains(new DegreePosition(2, 180)));
|
||||
assertFalse(b.contains(new DegreePosition(4, -178)));
|
||||
assertFalse(b.contains(new DegreePosition(0, 178)));
|
||||
assertFalse(b.contains(new DegreePosition(0, 180)));
|
||||
assertFalse(b.contains(new DegreePosition(2, 178)));
|
||||
assertFalse(b.contains(new DegreePosition(2, -178)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleUnionTestOverlap() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(3, 3));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(2, 2), new DegreePosition(4, 4));
|
||||
Bounds u = b1.union(b2);
|
||||
assertEquals(1.0, u.getSouthWest().getLatDeg(), 0.00000001);
|
||||
assertEquals(1.0, u.getSouthWest().getLngDeg(), 0.00000001);
|
||||
assertEquals(4.0, u.getNorthEast().getLatDeg(), 0.00000001);
|
||||
assertEquals(4.0, u.getNorthEast().getLngDeg(), 0.00000001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDatelineUnionOverlap() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 179), new DegreePosition(3, -179));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(2, 180), new DegreePosition(4, -178));
|
||||
Bounds u = b1.union(b2);
|
||||
assertEquals(1.0, u.getSouthWest().getLatDeg(), 0.00000001);
|
||||
assertEquals(179, u.getSouthWest().getLngDeg(), 0.00000001);
|
||||
assertEquals(4.0, u.getNorthEast().getLatDeg(), 0.00000001);
|
||||
assertEquals(-178, u.getNorthEast().getLngDeg(), 0.00000001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDatelineUnionOutside() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 179), new DegreePosition(3, -179));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(4, -178), new DegreePosition(5, -177));
|
||||
Bounds u = b1.union(b2);
|
||||
assertEquals(1.0, u.getSouthWest().getLatDeg(), 0.00000001);
|
||||
assertEquals(179, u.getSouthWest().getLngDeg(), 0.00000001);
|
||||
assertEquals(5.0, u.getNorthEast().getLatDeg(), 0.00000001);
|
||||
assertEquals(-177, u.getNorthEast().getLngDeg(), 0.00000001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDatelineUnionOneSideAndOtherSide() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 178), new DegreePosition(3, 179));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(4, -178), new DegreePosition(5, -177));
|
||||
Bounds u = b1.union(b2);
|
||||
assertEquals(1.0, u.getSouthWest().getLatDeg(), 0.00000001);
|
||||
assertEquals(178, u.getSouthWest().getLngDeg(), 0.00000001);
|
||||
assertEquals(5.0, u.getNorthEast().getLatDeg(), 0.00000001);
|
||||
assertEquals(-177, u.getNorthEast().getLngDeg(), 0.00000001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleUnionTestOutside() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(3, 3));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(4, 4), new DegreePosition(5, 5));
|
||||
Bounds u = b1.union(b2);
|
||||
assertEquals(1.0, u.getSouthWest().getLatDeg(), 0.00000001);
|
||||
assertEquals(1.0, u.getSouthWest().getLngDeg(), 0.00000001);
|
||||
assertEquals(5.0, u.getNorthEast().getLatDeg(), 0.00000001);
|
||||
assertEquals(5.0, u.getNorthEast().getLngDeg(), 0.00000001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleIntersectsTest() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(3, 3));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(2, 2), new DegreePosition(5, 5));
|
||||
assertTrue(b1.intersects(b2));
|
||||
assertTrue(b2.intersects(b1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleIntersectTest() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(3, 3));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(2, 2), new DegreePosition(5, 5));
|
||||
Bounds i = b1.intersect(b2);
|
||||
assertEquals(2, i.getSouthWest().getLatDeg(), 0.0000001);
|
||||
assertEquals(3, i.getNorthEast().getLatDeg(), 0.0000001);
|
||||
assertEquals(2, i.getSouthWest().getLngDeg(), 0.0000001);
|
||||
assertEquals(3, i.getNorthEast().getLngDeg(), 0.0000001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleNegativeIntersectsTest() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(3, 3));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(4, 4), new DegreePosition(5, 5));
|
||||
assertFalse(b1.intersects(b2));
|
||||
assertFalse(b2.intersects(b1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleNegativeIntersectTest() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(3, 3));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(4, 4), new DegreePosition(5, 5));
|
||||
Bounds i = b1.intersect(b2);
|
||||
assertTrue(i.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleNegativeIntersectsTestNonIntersectingLat() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(3, 3));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(4, 2), new DegreePosition(5, 5));
|
||||
assertFalse(b1.intersects(b2));
|
||||
assertFalse(b2.intersects(b1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleNegativeIntersectTestNonIntersectingLat() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(3, 3));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(4, 2), new DegreePosition(5, 5));
|
||||
Bounds i = b1.intersect(b2);
|
||||
assertTrue(i.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void intersectsWithContainsTest() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(4, 4));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(2, 2), new DegreePosition(3, 3));
|
||||
assertTrue(b1.intersects(b2));
|
||||
assertTrue(b2.intersects(b1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void intersectWithContainsTest() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 1), new DegreePosition(4, 4));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(2, 2), new DegreePosition(3, 3));
|
||||
Bounds i = b1.intersect(b2);
|
||||
assertEquals(2, i.getSouthWest().getLatDeg(), 0.0000001);
|
||||
assertEquals(3, i.getNorthEast().getLatDeg(), 0.0000001);
|
||||
assertEquals(2, i.getSouthWest().getLngDeg(), 0.0000001);
|
||||
assertEquals(3, i.getNorthEast().getLngDeg(), 0.0000001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dateLineIntersectsTest() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 178), new DegreePosition(3, -177));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(2, -178), new DegreePosition(5, -176));
|
||||
assertTrue(b1.intersects(b2));
|
||||
assertTrue(b2.intersects(b1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dateLineIntersectTest() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 178), new DegreePosition(3, -177));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(2, -178), new DegreePosition(5, -176));
|
||||
Bounds i = b1.intersect(b2);
|
||||
assertEquals(2, i.getSouthWest().getLatDeg(), 0.0000001);
|
||||
assertEquals(3, i.getNorthEast().getLatDeg(), 0.0000001);
|
||||
assertEquals(-178, i.getSouthWest().getLngDeg(), 0.0000001);
|
||||
assertEquals(-177, i.getNorthEast().getLngDeg(), 0.0000001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dateLineNegativeIntersectsTestNonMatchingLat() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 178), new DegreePosition(3, -177));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(4, -178), new DegreePosition(5, -176));
|
||||
assertFalse(b1.intersects(b2));
|
||||
assertFalse(b2.intersects(b1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dateLineNegativeIntersectTestNonMatchingLat() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 178), new DegreePosition(3, -177));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(4, -178), new DegreePosition(5, -176));
|
||||
Bounds i = b1.intersect(b2);
|
||||
assertTrue(i.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dateLineNegativeIntersectsTest() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 178), new DegreePosition(3, 179));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(2, -178), new DegreePosition(5, -177));
|
||||
assertFalse(b1.intersects(b2));
|
||||
assertFalse(b2.intersects(b1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dateLineNegativeIntersectTest() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 178), new DegreePosition(3, 179));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(2, -178), new DegreePosition(5, -177));
|
||||
Bounds i = b1.intersect(b2);
|
||||
assertTrue(i.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dateLineNegativeIntersectsTestBoundsCrossingDateLine() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 178), new DegreePosition(3, -179));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(2, -178), new DegreePosition(5, -177));
|
||||
assertFalse(b1.intersects(b2));
|
||||
assertFalse(b2.intersects(b1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dateLineNegativeIntersectTestBoundsCrossingDateLine() {
|
||||
Bounds b1 = new BoundsImpl(new DegreePosition(1, 178), new DegreePosition(3, -179));
|
||||
Bounds b2 = new BoundsImpl(new DegreePosition(2, -178), new DegreePosition(5, -177));
|
||||
Bounds i = b1.intersect(b2);
|
||||
assertTrue(i.isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.ControlPoint;
|
||||
import com.sap.sailing.domain.base.Course;
|
||||
import com.sap.sailing.domain.base.CourseArea;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Series;
|
||||
import com.sap.sailing.domain.base.Sideline;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.BoatClassImpl;
|
||||
import com.sap.sailing.domain.base.impl.CompetitorImpl;
|
||||
import com.sap.sailing.domain.base.impl.CourseImpl;
|
||||
import com.sap.sailing.domain.base.impl.DomainFactoryImpl;
|
||||
import com.sap.sailing.domain.base.impl.DynamicBoat;
|
||||
import com.sap.sailing.domain.base.impl.DynamicTeam;
|
||||
import com.sap.sailing.domain.base.impl.FleetImpl;
|
||||
import com.sap.sailing.domain.base.impl.MarkImpl;
|
||||
import com.sap.sailing.domain.base.impl.RegattaImpl;
|
||||
import com.sap.sailing.domain.base.impl.SeriesImpl;
|
||||
import com.sap.sailing.domain.base.impl.WaypointImpl;
|
||||
import com.sap.sailing.domain.common.Color;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
||||
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
|
||||
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
|
||||
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
|
||||
import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegatta;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
||||
|
||||
public class LeaderboardCourseChangeTest {
|
||||
|
||||
/**
|
||||
* See bug 2011
|
||||
*
|
||||
* @throws NoWindException
|
||||
* @throws InterruptedException
|
||||
* @throws ExecutionException
|
||||
*/
|
||||
@Test
|
||||
public void testLeaderboardDTOCreationForCourseChange() throws NoWindException, InterruptedException, ExecutionException {
|
||||
|
||||
Date date = Calendar.getInstance().getTime();
|
||||
TimePoint timePoint = new MillisecondsTimePoint(date);
|
||||
|
||||
TrackedRegattaRegistry trackedRegattaRegistry = mock(TrackedRegattaRegistry.class);
|
||||
|
||||
Fleet fleet = new FleetImpl("TestFleet");
|
||||
Series series = createSeries(trackedRegattaRegistry, fleet);
|
||||
Set<Series> seriesSet = new HashSet<>();
|
||||
seriesSet.add(series);
|
||||
|
||||
BoatClass boatClass = new BoatClassImpl("TestClass", true);
|
||||
|
||||
String raceColumnName = "TestRace";
|
||||
RaceColumn raceColumn = series.addRaceColumn(raceColumnName, trackedRegattaRegistry);
|
||||
ScoringScheme scoringScheme = new LowPoint();
|
||||
Regatta mockedRegatta = new RegattaImpl("TestRegatta", boatClass, seriesSet, false, scoringScheme,
|
||||
UUID.randomUUID(), mock(CourseArea.class));
|
||||
ControlPoint start = new MarkImpl("Start");
|
||||
ControlPoint m1 = new MarkImpl("M1");
|
||||
ControlPoint m2 = new MarkImpl("M2");
|
||||
ControlPoint finish = new MarkImpl("Finish");
|
||||
Set<Waypoint> waypoints = new HashSet<>();
|
||||
Waypoint startWaypoint = new WaypointImpl(start);
|
||||
waypoints.add(startWaypoint);
|
||||
Waypoint m1Waypoint1 = new WaypointImpl(m1);
|
||||
Waypoint m2Waypoint1 = new WaypointImpl(m2);
|
||||
Waypoint m1Waypoint2 = new WaypointImpl(m1);
|
||||
Waypoint m2Waypoint2 = new WaypointImpl(m2);
|
||||
Waypoint m1Waypoint3 = new WaypointImpl(m1);
|
||||
waypoints.add(m1Waypoint1);
|
||||
waypoints.add(m2Waypoint1);
|
||||
waypoints.add(m1Waypoint2);
|
||||
waypoints.add(m2Waypoint2);
|
||||
waypoints.add(m1Waypoint3);
|
||||
Waypoint finishWaypoint = new WaypointImpl(finish);
|
||||
waypoints.add(finishWaypoint);
|
||||
Course course = new CourseImpl(raceColumnName, waypoints);
|
||||
TrackedRace mockedTrackedRace = createSpyedTrackedRace(mockedRegatta, course, timePoint, boatClass);
|
||||
raceColumn.setTrackedRace(fleet, mockedTrackedRace);
|
||||
Set<String> raceColumnNames = new HashSet<>();
|
||||
raceColumnNames.add(raceColumnName);
|
||||
|
||||
int[] ruleRaw = { 5, 3, 2 };
|
||||
ThresholdBasedResultDiscardingRule rule = new ThresholdBasedResultDiscardingRuleImpl(ruleRaw);
|
||||
Leaderboard leaderboard = new RegattaLeaderboardImpl(mockedRegatta, rule);
|
||||
|
||||
DomainFactory baseDomainFactory = new DomainFactoryImpl();
|
||||
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint, raceColumnNames, false,
|
||||
trackedRegattaRegistry, baseDomainFactory);
|
||||
|
||||
assertEquals(6, leaderboardDTO.rows.values().iterator().next().fieldsByRaceColumnName.values().iterator()
|
||||
.next().legDetails.size());
|
||||
|
||||
course.removeWaypoint(2);
|
||||
course.removeWaypoint(3);
|
||||
|
||||
leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint, raceColumnNames, false, trackedRegattaRegistry,
|
||||
baseDomainFactory);
|
||||
assertEquals(4, leaderboardDTO.rows.values().iterator().next().fieldsByRaceColumnName.values().iterator()
|
||||
.next().legDetails.size());
|
||||
|
||||
}
|
||||
|
||||
private TrackedRace createSpyedTrackedRace(Regatta regatta, Course course, TimePoint timePoint, BoatClass boatClass) {
|
||||
TrackedRegatta mockedTrackedRegatta = createMockedTrackedRegatta(regatta);
|
||||
RaceDefinition mockedRace = createMockedRace(course, boatClass);
|
||||
TrackedRace spyedTrackedRace = spy(new DynamicTrackedRaceImpl(mockedTrackedRegatta, mockedRace,
|
||||
new HashSet<Sideline>(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, 5000, 20000, 20000));
|
||||
|
||||
return spyedTrackedRace;
|
||||
}
|
||||
|
||||
private RaceDefinition createMockedRace(Course course, BoatClass boatClass) {
|
||||
RaceDefinition mockedRaceDefinition = mock(RaceDefinition.class);
|
||||
when(mockedRaceDefinition.getCompetitors()).thenReturn(new HashSet<Competitor>());
|
||||
when(mockedRaceDefinition.getCourse()).thenReturn(course);
|
||||
when(mockedRaceDefinition.getBoatClass()).thenReturn(new BoatClassImpl("TestClass", true));
|
||||
when(mockedRaceDefinition.getName()).thenReturn("TestRace");
|
||||
Iterable<Competitor> competitorSet = createCompetitorSet(boatClass);
|
||||
when(mockedRaceDefinition.getCompetitors()).thenReturn(competitorSet);
|
||||
return mockedRaceDefinition;
|
||||
}
|
||||
|
||||
private Iterable<Competitor> createCompetitorSet(BoatClass boatClass) {
|
||||
Set<Competitor> competitors = new HashSet<>();
|
||||
DynamicBoat mockedBoat = mock(DynamicBoat.class);
|
||||
when(mockedBoat.getBoatClass()).thenReturn(boatClass);
|
||||
competitors.add(new CompetitorImpl(UUID.randomUUID(), "TestCompetitor", Color.BLACK, mock(DynamicTeam.class),
|
||||
mockedBoat));
|
||||
return competitors;
|
||||
}
|
||||
|
||||
private TrackedRegatta createMockedTrackedRegatta(Regatta regatta) {
|
||||
TrackedRegatta mockedTrackedRegatta = mock(DynamicTrackedRegatta.class);
|
||||
when(mockedTrackedRegatta.getRegatta()).thenReturn(regatta);
|
||||
return mockedTrackedRegatta;
|
||||
}
|
||||
|
||||
private Series createSeries(TrackedRegattaRegistry trackedRegattaRegistry, Fleet fleet) {
|
||||
Set<Fleet> fleets = new HashSet<>();
|
||||
fleets.add(fleet);
|
||||
|
||||
Series series = new SeriesImpl("TestSeries", false, fleets, new HashSet<String>(), trackedRegattaRegistry);
|
||||
return series;
|
||||
}
|
||||
|
||||
}
|
||||
-1
@@ -80,7 +80,6 @@ public class MultipleClassesInRegattaTest {
|
||||
assertEquals("505", kiwotest2.getRegatta().getBoatClass().getName());
|
||||
assertEquals("49er", kiwotest3.getRegatta().getBoatClass().getName());
|
||||
assertEquals("STG", weym470may112014_2.getRegatta().getBoatClass().getName());
|
||||
assertNotSame(weym470may112014_2.getRegatta(), kiwotest1.getRegatta()); // expecting that each race gets a distinct default regatta
|
||||
assertNotSame(kiwotest1.getRegatta(), kiwotest2.getRegatta());
|
||||
assertNotSame(kiwotest1.getRegatta(), kiwotest3.getRegatta());
|
||||
assertNotSame(kiwotest2.getRegatta(), kiwotest3.getRegatta());
|
||||
|
||||
+7
@@ -28,6 +28,7 @@ import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaIdentifier;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
@@ -538,6 +539,12 @@ public class MockedTrackedRace implements DynamicTrackedRace {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tack getTack(SpeedWithBearing speedWithBearing, Wind wind, TimePoint timePoint) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wind getDirectionFromStartToNextMark(TimePoint at) {
|
||||
|
||||
+7
@@ -20,6 +20,7 @@ import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
@@ -315,6 +316,12 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tack getTack(SpeedWithBearing speedWithBearing, Wind wind, TimePoint timePoint) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackedRegatta getTrackedRegatta() {
|
||||
|
||||
+3
-2
@@ -372,7 +372,7 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
// This is particularly bad if a persistent regatta was loaded but a default regatta was accidentally created.
|
||||
// Then, there is no way but restart the server to get rid of this stale cache entry here.
|
||||
if (result == null) {
|
||||
result = new RegattaImpl(raceLogStore, race.getName(), boatClass, trackedRegattaRegistry,
|
||||
result = new RegattaImpl(raceLogStore, defaultRegattaNameAndBoatClass.getA(), boatClass, trackedRegattaRegistry,
|
||||
// use the low-point system as the default scoring scheme
|
||||
getBaseDomainFactory().createScoringScheme(ScoringSchemeType.LOW_POINT), race.getId(), null);
|
||||
regattaCache.put(key, result);
|
||||
@@ -389,7 +389,8 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
for (IRaceCompetitor competitor : race.getRaceCompetitors()) {
|
||||
competitorClassList.add(competitor.getCompetitor().getCompetitorClass());
|
||||
}
|
||||
Pair<String, BoatClass> defaultRegattaNameAndBoatClass = new Pair<String, BoatClass>(race.getName(), getDominantBoatClass(competitorClassList));
|
||||
Pair<String, BoatClass> defaultRegattaNameAndBoatClass = new Pair<String, BoatClass>(race.getEvent().getName(),
|
||||
getDominantBoatClass(competitorClassList));
|
||||
return defaultRegattaNameAndBoatClass;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -182,7 +182,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
|
||||
} else {
|
||||
startTime = null;
|
||||
}
|
||||
if (startTime != null) {
|
||||
if (trackedRace != null && startTime != null) {
|
||||
trackedRace.setStartTimeReceived(startTime);
|
||||
}
|
||||
final TimePoint startTrackingTime;
|
||||
@@ -196,7 +196,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
|
||||
} else {
|
||||
startTrackingTime = null;
|
||||
}
|
||||
if (startTrackingTime != null) {
|
||||
if (trackedRace != null && startTrackingTime != null) {
|
||||
trackedRace.setStartOfTrackingReceived(startTrackingTime);
|
||||
}
|
||||
final TimePoint endTrackingTime;
|
||||
@@ -210,7 +210,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
|
||||
} else {
|
||||
endTrackingTime = null;
|
||||
}
|
||||
if (endTrackingTime != null) {
|
||||
if (trackedRace != null && endTrackingTime != null) {
|
||||
trackedRace.setEndOfTrackingReceived(endTrackingTime);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-5
@@ -210,6 +210,8 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
||||
*/
|
||||
private boolean stopped;
|
||||
|
||||
private final TrackedRegattaRegistry trackedRegattaRegistry;
|
||||
|
||||
/**
|
||||
* Creates a race tracked for the specified URL/URIs and starts receiving all available existing and future push
|
||||
* data from there. Receiving continues until {@link #stop()} is called.
|
||||
@@ -294,6 +296,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
||||
WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, TrackedRegattaRegistry trackedRegattaRegistry)
|
||||
throws URISyntaxException, MalformedURLException, FileNotFoundException, SubscriberInitializationException {
|
||||
super();
|
||||
this.trackedRegattaRegistry = trackedRegattaRegistry;
|
||||
this.tractracRace = tractracRace;
|
||||
this.tractracEvent = tractracRace.getEvent();
|
||||
urls = createID(paramURL, liveURI, storedURI);
|
||||
@@ -593,11 +596,13 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
||||
updateStatusOfTrackedRaces();
|
||||
if (!stopped) {
|
||||
try {
|
||||
// See also bug 1517; with TracAPI we assume that when stopped(IEvent) is called by the TracAPI then
|
||||
// all subscriptions have received all their data and it's therefore safe to stop all subscriptions
|
||||
// at this point without missing any data.
|
||||
stop();
|
||||
} catch (InterruptedException e) {
|
||||
for (RaceDefinition race : getRaces()) {
|
||||
// See also bug 1517; with TracAPI we assume that when stopped(IEvent) is called by the TracAPI then
|
||||
// all subscriptions have received all their data and it's therefore safe to stop all subscriptions
|
||||
// at this point without missing any data.
|
||||
trackedRegattaRegistry.stopTracking(regatta, race);
|
||||
}
|
||||
} catch (InterruptedException | IOException e) {
|
||||
logger.log(Level.INFO, "Interrupted while trying to stop tracker "+this, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
|
||||
private static final Logger logger = Logger.getLogger(RegattaImpl.class.getName());
|
||||
private static final long serialVersionUID = 6509564189552478869L;
|
||||
private final Set<RaceDefinition> races;
|
||||
private Set<RaceDefinition> races;
|
||||
private final BoatClass boatClass;
|
||||
private transient Set<RegattaListener> regattaListeners;
|
||||
private List<? extends Series> series;
|
||||
@@ -185,6 +185,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
MasterDataImportInformation masterDataImportInformation = ongoingMasterDataImportInformation.get();
|
||||
if (masterDataImportInformation != null) {
|
||||
raceLogStore = masterDataImportInformation.getRaceLogStore();
|
||||
races = new HashSet<RaceDefinition>();
|
||||
} else {
|
||||
raceLogStore = EmptyRaceLogStore.INSTANCE;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,8 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
|
||||
for (Fleet fleet : fleets) {
|
||||
this.fleetsByName.put(fleet.getName(), fleet);
|
||||
}
|
||||
fleetsInAscendingOrder = new ArrayList<Fleet>(fleetsByName.values());
|
||||
fleetsInAscendingOrder = new ArrayList<Fleet>();
|
||||
Util.addAll(fleets, fleetsInAscendingOrder);
|
||||
Collections.sort(fleetsInAscendingOrder);
|
||||
List<RaceColumnInSeries> myRaceColumns = new ArrayList<RaceColumnInSeries>();
|
||||
this.raceColumns = myRaceColumns;
|
||||
|
||||
+34
-5
@@ -122,7 +122,7 @@ public interface Leaderboard extends LeaderboardBase {
|
||||
* For split fleets things can vary slightly. There, one fleet may complete a few races before the another fleet
|
||||
* starts with those races. In this case there isn't even any point in time at which all fleets have finished
|
||||
* exactly <i>n</i> races. Still, this method pretends such a time point would have existed, actually ignoring
|
||||
* the <i>times</i> at which a race took place but only looking at the resulting scores and discard.<p>
|
||||
* the <i>times</i> at which a race took place but only looking at the resulting scores and discards.<p>
|
||||
*
|
||||
* When computing the ranks after all columns up to and including the race column that is the key of the resulting
|
||||
* map, the method applies the discarding and tie breaking rules as they would have had to be applied had the races
|
||||
@@ -133,6 +133,25 @@ public interface Leaderboard extends LeaderboardBase {
|
||||
*/
|
||||
Map<RaceColumn, List<Competitor>> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException;
|
||||
|
||||
/**
|
||||
* Computes the competitor's total points sum as they were or would have been after each race column (from left to right)
|
||||
* was completed.<p>
|
||||
*
|
||||
* A leaderboard fills up over time, usually "from left to right" with one race after another finishing.
|
||||
* For split fleets things can vary slightly. There, one fleet may complete a few races before the another fleet
|
||||
* starts with those races. In this case there isn't even any point in time at which all fleets have finished
|
||||
* exactly <i>n</i> races. Still, this method pretends such a time point would have existed, actually ignoring
|
||||
* the <i>times</i> at which a race took place but only looking at the resulting scores and discards.<p>
|
||||
*
|
||||
* When computing the total points sum after all columns up to and including the race column that is the key of the resulting
|
||||
* map, the method applies the discarding and tie breaking rules as they would have had to be applied had the races
|
||||
* in the respective column just completed.
|
||||
*
|
||||
* @return The resulting map is guaranteed to have the same iteration order regarding the race columns
|
||||
* as {@link #getRaceColumns()}.
|
||||
*/
|
||||
Map<RaceColumn, Map<Competitor, Double>> getTotalPointsSumAfterRaceColumn(TimePoint timePoint) throws NoWindException;
|
||||
|
||||
/**
|
||||
* Tells the number of points carried over from previous races not tracked by this leaderboard for
|
||||
* the <code>competitor</code>. Returns <code>0</code> if there is no carried points definition for
|
||||
@@ -216,6 +235,14 @@ public interface Leaderboard extends LeaderboardBase {
|
||||
*/
|
||||
Double getTotalPoints(Competitor competitor, TimePoint timePoint) throws NoWindException;
|
||||
|
||||
/**
|
||||
* Sums up the {@link #getTotalPoints(Competitor, RaceColumn, TimePoint) total points} of <code>competitor</code>
|
||||
* across all race columns listed in <code>raceColumnsToconsider</code>, respecting the
|
||||
* {@link RaceColumn#isStartsWithZeroScore()} property.
|
||||
*/
|
||||
Double getTotalPoints(Competitor competitor, Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint)
|
||||
throws NoWindException;
|
||||
|
||||
/**
|
||||
* Sorts the competitors according to their ranking in the race column specified. Only competitors who have a score
|
||||
* are added to the result list. This excludes competitors whose fleet hasn't raced for the <code>raceColumn</code>
|
||||
@@ -312,10 +339,11 @@ public interface Leaderboard extends LeaderboardBase {
|
||||
* Tells if the column represented by <code>raceColumn</code> shall be considered when counting the number of "races
|
||||
* so far" for discarding. Although medal races are never discarded themselves, they still count in determining the
|
||||
* number of "races so far" which is then the basis for deciding how many races may be discarded. If a leaderboard
|
||||
* has corrections for a column for the <code>competitor</code> then that column shall be considered for discarding
|
||||
* and counts for determining the number of races so far. Also, if a tracked race is connected to the column and has
|
||||
* started already, the column is to be considered for discarding unless the column has several unordered fleets and
|
||||
* not all fleets have started their race yet (see
|
||||
* has corrections for a column for the <code>competitor</code> that are already to be applied at
|
||||
* <code>timePoint</code> (e.g., because the race can be proven to have finished at <code>timePoint</code>), then
|
||||
* that column shall be considered for discarding and counts for determining the number of races so far. Also, if a
|
||||
* tracked race is connected to the column and has started already, the column is to be considered for discarding
|
||||
* unless the column has several unordered fleets and not all fleets have started their race yet (see
|
||||
* {@link ScoringScheme#isValidInTotalScore(Leaderboard, RaceColumn, TimePoint)}).
|
||||
*/
|
||||
boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint);
|
||||
@@ -459,4 +487,5 @@ public interface Leaderboard extends LeaderboardBase {
|
||||
InterruptedException, ExecutionException;
|
||||
|
||||
NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher();
|
||||
|
||||
}
|
||||
+28
-31
@@ -1,10 +1,11 @@
|
||||
package com.sap.sailing.domain.leaderboard;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.CompetitorChangeListener;
|
||||
@@ -29,6 +30,7 @@ import com.sap.sailing.domain.tracking.RaceChangeListener;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
|
||||
import com.sap.sailing.domain.tracking.Wind;
|
||||
import com.sap.sailing.util.impl.ConcurrentWeakHashMap;
|
||||
import com.sap.sailing.util.impl.LockUtil;
|
||||
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
|
||||
|
||||
@@ -45,7 +47,7 @@ public class LeaderboardCacheManager {
|
||||
private final WeakHashMap<Leaderboard, CacheInvalidationUponScoreCorrectionListener> scoreCorrectionListeners;
|
||||
private final NamedReentrantReadWriteLock scoreCorrectionAndCompetitorChangeListenersLock;
|
||||
private final WeakHashMap<Leaderboard, CacheInvalidationUponCompetitorChangeListener> competitorChangeListeners;
|
||||
private final WeakHashMap<Leaderboard, Map<TrackedRace, Set<CacheInvalidationListener>>> invalidationListenersPerLeaderboard;
|
||||
private final ConcurrentWeakHashMap<Leaderboard, ConcurrentHashMap<TrackedRace, Set<CacheInvalidationListener>>> invalidationListenersPerLeaderboard;
|
||||
private final WeakHashMap<Leaderboard, RaceColumnListener> raceColumnListeners;
|
||||
|
||||
private class CacheInvalidationListener implements RaceChangeListener {
|
||||
@@ -103,6 +105,16 @@ public class LeaderboardCacheManager {
|
||||
removeFromCache(leaderboard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
|
||||
removeFromCache(leaderboard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
|
||||
removeFromCache(leaderboard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delayToLiveChanged(long delayToLiveInMillis) {
|
||||
removeFromCache(leaderboard);
|
||||
@@ -209,7 +221,7 @@ public class LeaderboardCacheManager {
|
||||
|
||||
public LeaderboardCacheManager(LeaderboardCache leaderboardCache) {
|
||||
this.leaderboardCache = leaderboardCache;
|
||||
this.invalidationListenersPerLeaderboard = new WeakHashMap<>();
|
||||
this.invalidationListenersPerLeaderboard = new ConcurrentWeakHashMap<>();
|
||||
this.raceColumnListeners = new WeakHashMap<Leaderboard, RaceColumnListener>();
|
||||
this.scoreCorrectionListeners = new WeakHashMap<>();
|
||||
this.scoreCorrectionAndCompetitorChangeListenersLock = new NamedReentrantReadWriteLock(
|
||||
@@ -218,11 +230,10 @@ public class LeaderboardCacheManager {
|
||||
}
|
||||
|
||||
private void removeFromCache(Leaderboard leaderboard) {
|
||||
synchronized (invalidationListenersPerLeaderboard) {
|
||||
Map<TrackedRace, Set<CacheInvalidationListener>> listenersMap = invalidationListenersPerLeaderboard
|
||||
.remove(leaderboard);
|
||||
if (listenersMap != null) {
|
||||
for (Map.Entry<TrackedRace, Set<CacheInvalidationListener>> e : listenersMap.entrySet()) {
|
||||
ConcurrentHashMap<TrackedRace, Set<CacheInvalidationListener>> listenersMap = invalidationListenersPerLeaderboard.remove(leaderboard);
|
||||
if (listenersMap != null) {
|
||||
for (Map.Entry<TrackedRace, Set<CacheInvalidationListener>> e : listenersMap.entrySet()) {
|
||||
synchronized (e.getValue()) { // the Set is a Collections.synchronizedSet, and synchronization is required for iteration
|
||||
for (CacheInvalidationListener listener : e.getValue()) {
|
||||
listener.removeFromTrackedRace();
|
||||
}
|
||||
@@ -305,17 +316,7 @@ public class LeaderboardCacheManager {
|
||||
|
||||
@Override
|
||||
public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
|
||||
removeFromCache(leaderboard);
|
||||
Map<TrackedRace, Set<CacheInvalidationListener>> listenersMap = invalidationListenersPerLeaderboard
|
||||
.get(leaderboard);
|
||||
if (listenersMap != null) {
|
||||
Set<CacheInvalidationListener> listeners = listenersMap.get(trackedRace);
|
||||
if (listeners != null) {
|
||||
for (CacheInvalidationListener listener : listeners) {
|
||||
listener.removeFromTrackedRace();
|
||||
}
|
||||
}
|
||||
}
|
||||
removeFromCache(leaderboard); // removes all listeners from invalidationListenersPerLeaderboard and from their TrackedRaces
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -393,24 +394,20 @@ public class LeaderboardCacheManager {
|
||||
}
|
||||
|
||||
private void registerListener(final Leaderboard leaderboard, TrackedRace trackedRace) {
|
||||
Map<TrackedRace, Set<CacheInvalidationListener>> invalidationListeners;
|
||||
ConcurrentHashMap<TrackedRace, Set<CacheInvalidationListener>> invalidationListeners;
|
||||
final CacheInvalidationListener listener;
|
||||
synchronized (invalidationListenersPerLeaderboard) {
|
||||
listener = new CacheInvalidationListener(leaderboard, trackedRace);
|
||||
trackedRace.addListener(listener);
|
||||
invalidationListeners = invalidationListenersPerLeaderboard.get(leaderboard);
|
||||
if (invalidationListeners == null) {
|
||||
invalidationListeners = new HashMap<TrackedRace, Set<CacheInvalidationListener>>();
|
||||
invalidationListenersPerLeaderboard.put(leaderboard, invalidationListeners);
|
||||
}
|
||||
listener = new CacheInvalidationListener(leaderboard, trackedRace);
|
||||
trackedRace.addListener(listener);
|
||||
invalidationListeners = invalidationListenersPerLeaderboard.get(leaderboard);
|
||||
if (invalidationListeners == null) {
|
||||
invalidationListeners = new ConcurrentHashMap<TrackedRace, Set<CacheInvalidationListener>>();
|
||||
invalidationListenersPerLeaderboard.put(leaderboard, invalidationListeners);
|
||||
}
|
||||
Set<CacheInvalidationListener> listeners = invalidationListeners.get(trackedRace);
|
||||
if (listeners == null) {
|
||||
listeners = new HashSet<CacheInvalidationListener>();
|
||||
listeners = Collections.synchronizedSet(new HashSet<CacheInvalidationListener>());
|
||||
invalidationListeners.put(trackedRace, listeners);
|
||||
}
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+1
-1
@@ -88,7 +88,7 @@ public class LeaderboardDTOCache implements LeaderboardCache {
|
||||
private static final long serialVersionUID = 7287916997229815039L;
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<Util.Triple<TimePoint, Collection<String>, Boolean>, FutureTask<LeaderboardDTO>> e) {
|
||||
return size() > 10; // remember 10 LeaderboardDTOs per leaderborad
|
||||
return size() > 10; // remember 10 LeaderboardDTOs per leaderboard
|
||||
}
|
||||
};
|
||||
leaderboardCacheLock = new NamedReentrantReadWriteLock("leaderboardCacheLock for "+leaderboard.getName(), /* fair */ false);
|
||||
|
||||
+60
-4
@@ -287,6 +287,16 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statusChanged(TrackedRaceStatus newStatus) {
|
||||
// when the status changes away from LOADING, calculations may start or resume, making it necessary to clear the cache
|
||||
@@ -520,15 +530,21 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
|
||||
@Override
|
||||
public Double getTotalPoints(Competitor competitor, TimePoint timePoint) throws NoWindException {
|
||||
return getTotalPoints(competitor, getRaceColumns(), timePoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getTotalPoints(Competitor competitor, final Iterable<RaceColumn> raceColumnsToConsider,
|
||||
TimePoint timePoint) throws NoWindException {
|
||||
// when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on
|
||||
boolean needToResetScoreUponNextNonEmptyEntry = false;
|
||||
double result = getCarriedPoints(competitor);
|
||||
for (RaceColumn r : getRaceColumns()) {
|
||||
if (r.isStartsWithZeroScore()) {
|
||||
for (RaceColumn raceColumn : raceColumnsToConsider) {
|
||||
if (raceColumn.isStartsWithZeroScore()) {
|
||||
needToResetScoreUponNextNonEmptyEntry = true;
|
||||
}
|
||||
if (getScoringScheme().isValidInTotalScore(this, r, timePoint)) {
|
||||
final Double totalPoints = getTotalPoints(competitor, r, timePoint);
|
||||
if (getScoringScheme().isValidInTotalScore(this, raceColumn, timePoint)) {
|
||||
final Double totalPoints = getTotalPoints(competitor, raceColumn, raceColumnsToConsider, timePoint);
|
||||
if (totalPoints != null) {
|
||||
if (needToResetScoreUponNextNonEmptyEntry) {
|
||||
result = 0;
|
||||
@@ -690,6 +706,46 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<RaceColumn, Map<Competitor, Double>> getTotalPointsSumAfterRaceColumn(final TimePoint timePoint)
|
||||
throws NoWindException {
|
||||
final Map<RaceColumn, Map<Competitor, Double>> result = new LinkedHashMap<>();
|
||||
List<RaceColumn> raceColumnsToConsider = new ArrayList<>();
|
||||
Map<RaceColumn, Future<Map<Competitor, Double>>> futures = new HashMap<>();
|
||||
for (final RaceColumn raceColumn : getRaceColumns()) {
|
||||
raceColumnsToConsider.add(raceColumn);
|
||||
final Iterable<RaceColumn> finalRaceColumnsToConsider = new ArrayList<>(raceColumnsToConsider);
|
||||
futures.put(raceColumn, executor.submit(new Callable<Map<Competitor, Double>>() {
|
||||
@Override
|
||||
public Map<Competitor, Double> call() {
|
||||
Map<Competitor, Double> totalPointsSumPerCompetitorInColumn = new HashMap<>();
|
||||
for (Competitor competitor : getCompetitors()) {
|
||||
try {
|
||||
totalPointsSumPerCompetitorInColumn.put(competitor, getTotalPoints(competitor, finalRaceColumnsToConsider, timePoint));
|
||||
} catch (NoWindException e) {
|
||||
throw new NoWindError(e);
|
||||
}
|
||||
}
|
||||
synchronized (result) {
|
||||
return totalPointsSumPerCompetitorInColumn;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
for (RaceColumn raceColumn : getRaceColumns()) {
|
||||
try {
|
||||
result.put(raceColumn, futures.get(raceColumn).get());
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
if (e.getCause() instanceof NoWindError) {
|
||||
throw ((NoWindError) e.getCause()).getCause();
|
||||
} else {
|
||||
throw new RuntimeException(e); // no caught exceptions occur in the futures executed
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> getContent(final TimePoint timePoint) throws NoWindException {
|
||||
Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> result = new HashMap<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry>();
|
||||
|
||||
+2
-1
@@ -3,13 +3,14 @@ package com.sap.sailing.domain.tracking;
|
||||
import java.util.Map;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.CourseListener;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
|
||||
|
||||
public interface RaceChangeListener {
|
||||
public interface RaceChangeListener extends CourseListener {
|
||||
void competitorPositionChanged(GPSFixMoving fix, Competitor competitor);
|
||||
|
||||
void markPositionChanged(GPSFix fix, Mark mark);
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.TimingConstants;
|
||||
@@ -381,6 +382,17 @@ public interface TrackedRace extends Serializable {
|
||||
*/
|
||||
Tack getTack(Competitor competitor, TimePoint timePoint) throws NoWindException;
|
||||
|
||||
/**
|
||||
* Determines whether the <code>competitor</code> is sailing on port or starboard tack at the <code>timePoint</code>
|
||||
* requested.
|
||||
* <p>
|
||||
* This method outperforms {@link #getTack(Competitor, TimePoint)}, based on being passed an already calculated wind
|
||||
* for the given time and competitor position as well as the competitors speed and course over ground.
|
||||
* <p>
|
||||
* This method will acquire the read lock for the competitor's track.
|
||||
*/
|
||||
Tack getTack(SpeedWithBearing speedWithBearing, Wind wind, TimePoint timePoint);
|
||||
|
||||
TrackedRegatta getTrackedRegatta();
|
||||
|
||||
/**
|
||||
@@ -578,8 +590,11 @@ public interface TrackedRace extends Serializable {
|
||||
RaceLog getRaceLog(Serializable identifier);
|
||||
|
||||
/**
|
||||
* a setter for the listener on course design changes. The listener is mostly part of the tracking provider adapter.
|
||||
* @param listener the listener to operate with.
|
||||
* A setter for the listener on course design changes suggested by one of the {@link RaceLog}s attached to this
|
||||
* race. The listener is mostly part of the tracking provider adapter.
|
||||
*
|
||||
* @param listener
|
||||
* the listener to operate with.
|
||||
*/
|
||||
void addCourseDesignChangedListener(CourseDesignChangedListener listener);
|
||||
|
||||
|
||||
+8
@@ -59,5 +59,13 @@ public abstract class AbstractRaceChangeListener implements RaceChangeListener {
|
||||
@Override
|
||||
public void delayToLiveChanged(long delayToLiveInMillis) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
|
||||
}
|
||||
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
package com.sap.sailing.domain.tracking.impl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.tracking.GPSFix;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.RaceChangeListener;
|
||||
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
|
||||
import com.sap.sailing.domain.tracking.Wind;
|
||||
|
||||
public class BaseRaceChangeListener implements RaceChangeListener {
|
||||
|
||||
@Override
|
||||
public void competitorPositionChanged(GPSFixMoving fix, Competitor competitor) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markPositionChanged(GPSFix fix, Mark mark) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markPassingReceived(Competitor competitor, Map<Waypoint, MarkPassing> oldMarkPassings,
|
||||
Iterable<MarkPassing> markPassings) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windDataReceived(Wind wind, WindSource windSource) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windDataRemoved(Wind wind, WindSource windSource) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceTimesChanged(TimePoint startOfTracking, TimePoint endOfTracking, TimePoint startTimeReceived) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delayToLiveChanged(long delayToLiveInMillis) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windSourcesToExcludeChanged(Iterable<? extends WindSource> windSourcesToExclude) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statusChanged(TrackedRaceStatus newStatus) {
|
||||
}
|
||||
|
||||
}
|
||||
+35
-1
@@ -85,6 +85,15 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
|
||||
|
||||
private static final long serialVersionUID = 4884868659665863604L;
|
||||
|
||||
public void deleteAll() {
|
||||
lockForWrite();
|
||||
try {
|
||||
getInternalRawFixes().clear();
|
||||
} finally {
|
||||
unlockAfterWrite();
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteAllLaterThan(TimePoint from) {
|
||||
// TODO use a specialized ArrayList in a specialized ArrayListNavigableSet and then make removeRange public
|
||||
lockForWrite();
|
||||
@@ -171,7 +180,11 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
|
||||
FromTimePointToEndUpdateInterval updateInterval) {
|
||||
CrossTrackErrorSumAndNumberOfFixesTrack result;
|
||||
if (oldValue != null) {
|
||||
oldValue.deleteAllLaterThan(updateInterval.getFrom());
|
||||
if (updateInterval == null) {
|
||||
oldValue.deleteAll();
|
||||
} else {
|
||||
oldValue.deleteAllLaterThan(updateInterval.getFrom());
|
||||
}
|
||||
computedCacheUpdate.lockForRead();
|
||||
try {
|
||||
for (CrossTrackErrorSumAndNumberOfFixes entry : computedCacheUpdate.getRawFixes()) {
|
||||
@@ -434,6 +447,27 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all cache contents for all competitors from the beginning of time
|
||||
*/
|
||||
private void invalidate() {
|
||||
final List<Competitor> shuffledCompetitors = new ArrayList<>(cachePerCompetitor.keySet());
|
||||
Collections.shuffle(shuffledCompetitors);
|
||||
for (Competitor competitor : shuffledCompetitors) {
|
||||
cachePerCompetitor.triggerUpdate(competitor, null /* meaning: from the beginning of time */);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CrossTrackErrorCache for competitors "+cachePerCompetitor.keySet();
|
||||
|
||||
+42
@@ -362,6 +362,48 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListenersWaypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
|
||||
RaceChangeListener[] listeners;
|
||||
synchronized (getListeners()) {
|
||||
listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]);
|
||||
}
|
||||
for (RaceChangeListener listener : listeners) {
|
||||
try {
|
||||
listener.waypointAdded(zeroBasedIndex, waypointThatGotAdded);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.log(Level.SEVERE, "notifyListenersWaypointAdded(int, Waypoint)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyListenersWaypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
|
||||
RaceChangeListener[] listeners;
|
||||
synchronized (getListeners()) {
|
||||
listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]);
|
||||
}
|
||||
for (RaceChangeListener listener : listeners) {
|
||||
try {
|
||||
listener.waypointRemoved(zeroBasedIndex, waypointThatGotRemoved);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage());
|
||||
logger.log(Level.SEVERE, "notifyListenersWaypointRemoved(int, Waypoint)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
|
||||
super.waypointAdded(zeroBasedIndex, waypointThatGotAdded);
|
||||
notifyListenersWaypointAdded(zeroBasedIndex, waypointThatGotAdded);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
|
||||
super.waypointRemoved(zeroBasedIndex, waypointThatGotRemoved);
|
||||
notifyListenersWaypointRemoved(zeroBasedIndex, waypointThatGotRemoved);
|
||||
}
|
||||
|
||||
private void notifyListeners(GPSFix fix, Mark mark) {
|
||||
RaceChangeListener[] listeners;
|
||||
synchronized (getListeners()) {
|
||||
|
||||
+10
@@ -677,6 +677,16 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl impl
|
||||
clearCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
|
||||
clearCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
|
||||
clearCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markPositionChanged(GPSFix fix, Mark mark) {
|
||||
assert fix != null && fix.getTimePoint() != null;
|
||||
|
||||
+10
@@ -198,6 +198,16 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
|
||||
clearCaches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
|
||||
clearCaches(); // necessary because the competitor tracks ordered by rank may change for legs adjacent to the waypoint added
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
|
||||
clearCaches(); // necessary because the competitor tracks ordered by rank may change for legs adjacent to the waypoint removed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statusChanged(TrackedRaceStatus newStatus) {
|
||||
// no-op; the leg doesn't mind the tracked race's status being updated
|
||||
|
||||
+96
-37
@@ -17,6 +17,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
@@ -178,7 +179,12 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
|
||||
private long updateCount;
|
||||
|
||||
private transient Map<TimePoint, List<Competitor>> competitorRankings;
|
||||
/**
|
||||
* Limit for the cache size in {@link #competitorRankings} and respectively in {@link #competitorRankingsLocks}.
|
||||
*/
|
||||
private static final int MAX_COMPETITOR_RANKINGS_CACHE_SIZE = 10;
|
||||
|
||||
private transient LinkedHashMap<TimePoint, List<Competitor>> competitorRankings;
|
||||
|
||||
/**
|
||||
* The locks managed here correspond with the {@link #competitorRankings} structure. When
|
||||
@@ -186,7 +192,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
* time point. Readers use the read lock. Checking / entering a lock into this map uses <code>synchronized</code> on
|
||||
* the map itself.
|
||||
*/
|
||||
private transient Map<TimePoint, NamedReentrantReadWriteLock> competitorRankingsLocks;
|
||||
private transient LinkedHashMap<TimePoint, NamedReentrantReadWriteLock> competitorRankingsLocks;
|
||||
|
||||
/**
|
||||
* legs appear in the order in which they appear in the race's course
|
||||
@@ -374,8 +380,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
WindSource trackBasedWindSource = new WindSourceImpl(WindSourceType.TRACK_BASED_ESTIMATION);
|
||||
windTracks.put(trackBasedWindSource,
|
||||
getOrCreateWindTrack(trackBasedWindSource, delayForWindEstimationCacheInvalidation));
|
||||
competitorRankings = new HashMap<TimePoint, List<Competitor>>();
|
||||
competitorRankingsLocks = new HashMap<TimePoint, NamedReentrantReadWriteLock>();
|
||||
competitorRankings = createCompetitorRankingsCache();
|
||||
competitorRankingsLocks = createCompetitorRankingsLockMap();
|
||||
// now wait until wind loading has at least started; then we know that the serialization lock is safely held by the loader
|
||||
try {
|
||||
waitUntilLoadingFromWindStoreComplete();
|
||||
@@ -384,6 +390,26 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedHashMap<TimePoint, NamedReentrantReadWriteLock> createCompetitorRankingsLockMap() {
|
||||
return new LinkedHashMap<TimePoint, NamedReentrantReadWriteLock>() {
|
||||
private static final long serialVersionUID = 6298801656693955386L;
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Entry<TimePoint, NamedReentrantReadWriteLock> eldest) {
|
||||
return size() > MAX_COMPETITOR_RANKINGS_CACHE_SIZE;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private LinkedHashMap<TimePoint, List<Competitor>> createCompetitorRankingsCache() {
|
||||
return new LinkedHashMap<TimePoint, List<Competitor>>() {
|
||||
private static final long serialVersionUID = -6044369612727021861L;
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Entry<TimePoint, List<Competitor>> eldest) {
|
||||
return size() > MAX_COMPETITOR_RANKINGS_CACHE_SIZE;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Assuming that the tracks were loaded from the persistent store, this method updates
|
||||
* the time stamps that frame the data held by this tracked race. See {@link #timePointOfLastEvent}, {@link #timePointOfNewestEvent}
|
||||
@@ -426,8 +452,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
markPassingsTimes = new ArrayList<com.sap.sse.common.Util.Pair<Waypoint, com.sap.sse.common.Util.Pair<TimePoint, TimePoint>>>();
|
||||
cacheInvalidationTimerLock = new Object();
|
||||
windStore = EmptyWindStore.INSTANCE;
|
||||
competitorRankings = new HashMap<TimePoint, List<Competitor>>();
|
||||
competitorRankingsLocks = new HashMap<TimePoint, NamedReentrantReadWriteLock>();
|
||||
competitorRankings = createCompetitorRankingsCache();
|
||||
competitorRankingsLocks = createCompetitorRankingsLockMap();
|
||||
directionFromStartToNextMarkCache = new HashMap<TimePoint, Future<Wind>>();
|
||||
crossTrackErrorCache = new CrossTrackErrorCache(this);
|
||||
maneuverCache = createManeuverCache();
|
||||
@@ -995,44 +1021,33 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
}
|
||||
}
|
||||
List<Competitor> rankedCompetitors;
|
||||
final boolean lockForWrite;
|
||||
synchronized (competitorRankings) {
|
||||
rankedCompetitors = competitorRankings.get(timePoint);
|
||||
if (rankedCompetitors == null) {
|
||||
lockForWrite = true;
|
||||
} else {
|
||||
lockForWrite = false;
|
||||
}
|
||||
}
|
||||
if (lockForWrite) {
|
||||
if (rankedCompetitors == null) {
|
||||
LockUtil.lockForWrite(readWriteLock);
|
||||
} else {
|
||||
LockUtil.lockForRead(readWriteLock);
|
||||
}
|
||||
try {
|
||||
if (rankedCompetitors == null) {
|
||||
rankedCompetitors = competitorRankings.get(timePoint); // try again; maybe a writer released the write
|
||||
// lock after updating the cache
|
||||
try {
|
||||
if (rankedCompetitors == null) {
|
||||
RaceRankComparator comparator = new RaceRankComparator(this, timePoint);
|
||||
rankedCompetitors = new ArrayList<Competitor>();
|
||||
for (Competitor c : getRace().getCompetitors()) {
|
||||
rankedCompetitors.add(c);
|
||||
}
|
||||
Collections.sort(rankedCompetitors, comparator);
|
||||
synchronized (competitorRankings) {
|
||||
competitorRankings.put(timePoint, rankedCompetitors);
|
||||
rankedCompetitors = competitorRankings.get(timePoint); // try again; maybe a writer released the
|
||||
// write
|
||||
// lock after updating the cache
|
||||
if (rankedCompetitors == null) {
|
||||
RaceRankComparator comparator = new RaceRankComparator(this, timePoint);
|
||||
rankedCompetitors = new ArrayList<Competitor>();
|
||||
for (Competitor c : getRace().getCompetitors()) {
|
||||
rankedCompetitors.add(c);
|
||||
}
|
||||
Collections.sort(rankedCompetitors, comparator);
|
||||
synchronized (competitorRankings) {
|
||||
competitorRankings.put(timePoint, rankedCompetitors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rankedCompetitors;
|
||||
} finally {
|
||||
if (lockForWrite) {
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(readWriteLock);
|
||||
} else {
|
||||
LockUtil.unlockAfterRead(readWriteLock);
|
||||
}
|
||||
}
|
||||
return rankedCompetitors;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1851,6 +1866,42 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is probably best explained by example. If the wind bearing is from port to starboard, the situation looks
|
||||
* like this:
|
||||
*
|
||||
* <pre>
|
||||
* ^
|
||||
* Wind | Boat
|
||||
* -----------> |
|
||||
* |
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* In this case, the boat gets the wind from port, so the result has to be {@link Tack#PORT}. The angle between the
|
||||
* boat's heading (which we can only approximate by the boat's course over ground) and the wind bearing in this case
|
||||
* is 90 degrees. <code>wind.{@link Bearing#getDifferenceTo(Bearing) getDifferenceTo}(boat)</code> in this case will
|
||||
* return a bearing representing -90 degrees.
|
||||
* <p>
|
||||
*
|
||||
* If the wind is blowing the other way, the angle returned by {@link Bearing#getDifferenceTo(Bearing)} will
|
||||
* correspond to +90 degrees. In other words, a negative angle means starboard tack, a positive angle represents
|
||||
* port tack.
|
||||
* <p>
|
||||
*
|
||||
* For the unlikely case of 0 degrees difference, {@link Tack#STARBOARD} will result.
|
||||
*
|
||||
* @return <code>null</code> in case the boat's bearing cannot be determined for <code>timePoint</code>
|
||||
*/
|
||||
@Override
|
||||
public Tack getTack(SpeedWithBearing estimatedSpeed, Wind wind, TimePoint timePoint) {
|
||||
Tack result = null;
|
||||
if (estimatedSpeed != null) {
|
||||
result = getTack(wind, estimatedSpeed.getBearing());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the wind direction at <code>timePoint</code> and at position <code>where</code>, compares the
|
||||
* <code>boatBearing</code> to the wind's bearing at that time and place and determined the tack.
|
||||
@@ -1861,15 +1912,22 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
*/
|
||||
private Tack getTack(Position where, TimePoint timePoint, Bearing boatBearing) throws NoWindException {
|
||||
final Wind wind = getWind(where, timePoint);
|
||||
Tack result;
|
||||
if (wind == null) {
|
||||
throw new NoWindException("Can't determine wind direction in position " + where + " at " + timePoint
|
||||
+ ", therefore cannot determine tack");
|
||||
}
|
||||
return getTack(wind, boatBearing);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Based on the wind, compares the <code>boatBearing</code> to the wind's bearing at
|
||||
* that time and place and determined the tack.
|
||||
*/
|
||||
private Tack getTack(Wind wind, Bearing boatBearing) {
|
||||
Bearing windBearing = wind.getBearing();
|
||||
Bearing difference = windBearing.getDifferenceTo(boatBearing);
|
||||
result = difference.getDegrees() <= 0 ? Tack.PORT : Tack.STARBOARD;
|
||||
return result;
|
||||
return difference.getDegrees() <= 0 ? Tack.PORT : Tack.STARBOARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2026,6 +2084,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
GPSFixMoving next = approximationPointsIter.next();
|
||||
speedWithBearingOnApproximationFromCurrentToNext = current.getSpeedAndBearingRequiredToReach(next);
|
||||
// compute course change on "approximation track"
|
||||
// FIXME bug 2009: when a maneuver (particularly a penalty circle) is executed at high turn rates, approximations may lead to turns >180deg, hence inferred to turn the wrong way; need to loop across the non-approximated fixes here!
|
||||
CourseChange courseChange = speedWithBearingOnApproximationFromPreviousToCurrent
|
||||
.getCourseChangeRequiredToReach(speedWithBearingOnApproximationFromCurrentToNext);
|
||||
com.sap.sse.common.Util.Pair<GPSFixMoving, CourseChange> courseChangeAtFix = new com.sap.sse.common.Util.Pair<GPSFixMoving, CourseChange>(current,
|
||||
|
||||
@@ -8,7 +8,8 @@ Bundle-Vendor: SAP
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
|
||||
Import-Package: com.sap.sailing.resultimport,
|
||||
com.sap.sailing.resultimport.impl,
|
||||
org.osgi.framework;version="1.3.0"
|
||||
org.osgi.framework;version="1.3.0",
|
||||
org.osgi.util.tracker;version="1.5.1"
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: com.sap.sailing.domain.common,
|
||||
com.sap.sailing.domain,
|
||||
|
||||
+22
-3
@@ -4,9 +4,11 @@ import java.util.logging.Logger;
|
||||
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.util.tracker.ServiceTracker;
|
||||
|
||||
import com.sap.sailing.domain.common.ScoreCorrectionProvider;
|
||||
import com.sap.sailing.resultimport.ResultUrlRegistry;
|
||||
import com.sap.sailing.resultimport.impl.ResultUrlRegistryServiceTrackerCustomizer;
|
||||
|
||||
public class Activator implements BundleActivator {
|
||||
private static final Logger logger = Logger.getLogger(Activator.class.getName());
|
||||
@@ -14,6 +16,8 @@ public class Activator implements BundleActivator {
|
||||
private static final String SCAN_DIR_PATH_PROPERTY_NAME = "freg.results";
|
||||
private static final String DEFAULT_SCAN_DIR = "fregresults";
|
||||
|
||||
private ServiceTracker<ResultUrlRegistry, ResultUrlRegistry> resultUrlRegistryServiceTracker;
|
||||
|
||||
/*
|
||||
* For testing, consider using the following URLs:
|
||||
* allUrls.add(new URL("http://www.axel-uhl.de/freg/freg_html_export_sample.html"));
|
||||
@@ -24,11 +28,26 @@ public class Activator implements BundleActivator {
|
||||
if (scanDirPath == null) {
|
||||
scanDirPath = System.getProperty(SCAN_DIR_PATH_PROPERTY_NAME, DEFAULT_SCAN_DIR);
|
||||
}
|
||||
final ScoreCorrectionProviderImpl service = new ScoreCorrectionProviderImpl(ResultUrlRegistry.INSTANCE);
|
||||
bundleContext.registerService(ScoreCorrectionProvider.class, service, /* properties */null);
|
||||
logger.info("Scanning " + scanDirPath + " for official result lists of " + service.getName());
|
||||
logger.info("Scanning " + scanDirPath + " for official result lists of "
|
||||
+ ScoreCorrectionProviderImpl.PROVIDER_NAME);
|
||||
|
||||
resultUrlRegistryServiceTracker = new ServiceTracker<>(bundleContext, ResultUrlRegistry.class,
|
||||
new ResultUrlRegistryServiceTrackerCustomizer(bundleContext) {
|
||||
|
||||
@Override
|
||||
protected ScoreCorrectionProvider configureScoreCorrectionProvider(
|
||||
ResultUrlRegistry resultUrlRegistry) {
|
||||
final ScoreCorrectionProviderImpl service = new ScoreCorrectionProviderImpl(resultUrlRegistry);
|
||||
return service;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
resultUrlRegistryServiceTracker.open();
|
||||
|
||||
}
|
||||
|
||||
public void stop(BundleContext bundleContext) throws Exception {
|
||||
resultUrlRegistryServiceTracker.close();
|
||||
}
|
||||
}
|
||||
+7
@@ -27,6 +27,7 @@ import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaIdentifier;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
@@ -539,6 +540,12 @@ public class MockedTrackedRace implements DynamicTrackedRace {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tack getTack(SpeedWithBearing speedWithBearing, Wind wind, TimePoint timePoint) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wind getDirectionFromStartToNextMark(TimePoint at) {
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.sap.sailing.gwt.ui.actions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
|
||||
import com.sap.sse.gwt.client.async.AsyncAction;
|
||||
|
||||
public class GetRaceTimesInfoAction implements AsyncAction<List<RaceTimesInfoDTO>> {
|
||||
private final SailingServiceAsync sailingService;
|
||||
private final Collection<RegattaAndRaceIdentifier> raceIdentifiers;
|
||||
|
||||
public GetRaceTimesInfoAction(SailingServiceAsync sailingService, Collection<RegattaAndRaceIdentifier> raceIdentifiers) {
|
||||
this.sailingService = sailingService;
|
||||
this.raceIdentifiers = raceIdentifiers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(AsyncCallback<List<RaceTimesInfoDTO>> callback) {
|
||||
sailingService.getRaceTimesInfos(raceIdentifiers, callback);
|
||||
}
|
||||
}
|
||||
+5
@@ -296,6 +296,11 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
|
||||
List<String> strings = new ArrayList<String>();
|
||||
strings.add(t.name);
|
||||
strings.add(t.regattaName);
|
||||
if (t.boatClassNames != null) {
|
||||
for (String boatClassName : t.boatClassNames) {
|
||||
strings.add(boatClassName);
|
||||
}
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
};
|
||||
|
||||
+3
-2
@@ -223,15 +223,16 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider {
|
||||
|
||||
@Override
|
||||
public Color getColor(CompetitorDTO competitor) {
|
||||
Color result = null;
|
||||
final Color result;
|
||||
if (allCompetitors.contains(competitor)) {
|
||||
if (competitor.getColor() != null) {
|
||||
result = competitor.getColor();
|
||||
} else {
|
||||
result = competitorsColorMap.getColorByID(competitor);
|
||||
}
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -28,6 +28,8 @@ public class DetailTypeFormatter {
|
||||
return stringMessages.totalPoints();
|
||||
case REGATTA_TOTAL_POINTS:
|
||||
return stringMessages.totalPoints();
|
||||
case REGATTA_TOTAL_POINTS_SUM:
|
||||
return stringMessages.totalPoints();
|
||||
case NUMBER_OF_MANEUVERS:
|
||||
return stringMessages.numberOfManeuvers();
|
||||
case CURRENT_SPEED_OVER_GROUND_IN_KNOTS:
|
||||
@@ -194,6 +196,7 @@ public class DetailTypeFormatter {
|
||||
case OVERALL_RANK:
|
||||
case RACE_TOTAL_POINTS:
|
||||
case REGATTA_TOTAL_POINTS:
|
||||
case REGATTA_TOTAL_POINTS_SUM:
|
||||
case CURRENT_LEG:
|
||||
case TACK:
|
||||
case START_TACK:
|
||||
@@ -293,6 +296,8 @@ public class DetailTypeFormatter {
|
||||
return stringMessages.raceTotalPointsTooltip();
|
||||
case REGATTA_TOTAL_POINTS:
|
||||
return stringMessages.regattaTotalPointsTooltip();
|
||||
case REGATTA_TOTAL_POINTS_SUM:
|
||||
return stringMessages.regattaTotalPointsTooltip();
|
||||
case RANK_GAIN:
|
||||
return stringMessages.rankGainTooltip();
|
||||
case TACK:
|
||||
|
||||
+7
-3
@@ -17,26 +17,29 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.actions.GetRaceTimesInfoAction;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
|
||||
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
|
||||
|
||||
public class RaceTimesInfoProvider {
|
||||
|
||||
private final SailingServiceAsync sailingService;
|
||||
private final AsyncActionsExecutor asyncActionsExecutor;
|
||||
private final ErrorReporter errorReporter;
|
||||
|
||||
private final Set<RegattaAndRaceIdentifier> raceIdentifiers;
|
||||
private long requestIntervalInMillis;
|
||||
|
||||
private final HashMap<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfos;
|
||||
|
||||
private final Set<RaceTimesInfoProviderListener> listeners;
|
||||
|
||||
/**
|
||||
* The <code>raceIdentifiers</code> has to be non-<code>null</code>, but can be empty.
|
||||
*/
|
||||
public RaceTimesInfoProvider(SailingServiceAsync sailingService, ErrorReporter errorReporter,
|
||||
public RaceTimesInfoProvider(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, ErrorReporter errorReporter,
|
||||
Collection<RegattaAndRaceIdentifier> raceIdentifiers, long requestIntervalInMillis) {
|
||||
this.sailingService = sailingService;
|
||||
this.asyncActionsExecutor = asyncActionsExecutor;
|
||||
this.errorReporter = errorReporter;
|
||||
this.raceIdentifiers = new HashSet<RegattaAndRaceIdentifier>(raceIdentifiers);
|
||||
this.requestIntervalInMillis = requestIntervalInMillis;
|
||||
@@ -97,7 +100,8 @@ public class RaceTimesInfoProvider {
|
||||
private void readTimesInfos() {
|
||||
if (!raceIdentifiers.isEmpty()) {
|
||||
final long clientTimeWhenRequestWasSent = System.currentTimeMillis();
|
||||
sailingService.getRaceTimesInfos(raceIdentifiers, new AsyncCallback<List<RaceTimesInfoDTO>>() {
|
||||
GetRaceTimesInfoAction getRaceTimesInfoAction = new GetRaceTimesInfoAction(sailingService, raceIdentifiers);
|
||||
asyncActionsExecutor.execute(getRaceTimesInfoAction, new AsyncCallback<List<RaceTimesInfoDTO>>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError("Error trying to obtain the race time infos: " + caught.getMessage(),
|
||||
|
||||
+1
-1
@@ -457,7 +457,7 @@ public interface SailingServiceAsync {
|
||||
|
||||
/**
|
||||
* @param detailType
|
||||
* supports {@link DetailType#REGATTA_RANK}, {@link DetailType#REGATTA_TOTAL_POINTS} and
|
||||
* supports {@link DetailType#REGATTA_RANK}, {@link DetailType#REGATTA_TOTAL_POINTS_SUM} and
|
||||
* {@link DetailType#OVERALL_RANK}.
|
||||
*
|
||||
* @return the first triple element is the race column name; then follows the list of competitors, and finally the
|
||||
|
||||
+3
-3
@@ -1044,7 +1044,7 @@ public interface StringMessages extends Messages {
|
||||
String eventLogoImageURL();
|
||||
String showPingMarkMappings();
|
||||
String regattaNameMustNotContainSlashes();
|
||||
String showNumberOfRacesCompleted();
|
||||
String racesCompleted();
|
||||
String racesCompletedTooltip();
|
||||
String showNumberOfRacesScored();
|
||||
String racesScored();
|
||||
String racesScoredTooltip();
|
||||
}
|
||||
+3
-3
@@ -1030,6 +1030,6 @@ eventOfficialWebsiteURL=Official Website URL
|
||||
eventLogoImageURL=Logo Image URL
|
||||
showPingMarkMappings=Show Mappings for pinged Marks
|
||||
regattaNameMustNotContainSlashes=Regatta name must not contain slashes
|
||||
showNumberOfRacesCompleted=Show number of races completed
|
||||
racesCompleted=Races completed
|
||||
racesCompletedTooltip=Number of races the competitor has completed in this leaderboard.
|
||||
showNumberOfRacesScored=Number of races scored
|
||||
racesScored=Races scored
|
||||
racesScoredTooltip=Number of races the competitor has completed or gotten a score for in this leaderboard.
|
||||
+3
-3
@@ -1024,6 +1024,6 @@ eventOfficialWebsiteURL=URL der offiziellen Veranstaltungs-Website
|
||||
eventLogoImageURL=URL des Veranstaltungs-Logos
|
||||
showPingMarkMappings=Mappings für gepingte Marks zeigen
|
||||
regattaNameMustNotContainSlashes=Regattaname darf keine Schrägstriche enthalten
|
||||
showNumberOfRacesCompleted=Anzahl vollendeter Rennen
|
||||
racesCompleted=Vollendete Rennen
|
||||
racesCompletedTooltip=Anzahl von Rennen, die der Segler vollendet hat
|
||||
showNumberOfRacesScored=Anzahl bepunkteter Rennen
|
||||
racesScored=Bepunktete Rennen
|
||||
racesScoredTooltip=Anzahl von Rennen, die der Segler vollendet oder für die er Punkte bekommen hat
|
||||
+4
-4
@@ -982,7 +982,7 @@ pleaseSelectAScoringResult=Please select a scoring result...
|
||||
eventOfficialWebsiteURL=Official Website URL
|
||||
eventLogoImageURL=Logo Image URL
|
||||
showPingMarkMappings=Show Mappings for pinged Marks
|
||||
regattaNameMustNotContainSlashes=Regatta name must not contain slashes
|
||||
showNumberOfRacesCompleted=Show number of races completed
|
||||
racesCompleted=Races completed
|
||||
racesCompletedTooltip=Number of races the competitor has completed in this leaderboard.
|
||||
showNumberOfRacesScored=Показать количество завершенных гонок
|
||||
racesScored=Завершенные гонки
|
||||
racesScoredTooltip=Количество гонок завершенное участником в данной таблице лидеров
|
||||
regattaNameMustNotContainSlashes=Regatta name must not contain slashes
|
||||
+9
-13
@@ -150,7 +150,7 @@ public abstract class AbstractCompetitorLeaderboardChart<SettingsType> extends A
|
||||
chart.getYAxis().setReversed(true);
|
||||
chart.getYAxis().setTickInterval(1.0);
|
||||
break;
|
||||
case REGATTA_TOTAL_POINTS:
|
||||
case REGATTA_TOTAL_POINTS_SUM:
|
||||
chart.getYAxis().setTickInterval(5.0);
|
||||
chart.getYAxis().setReversed(false);
|
||||
break;
|
||||
@@ -231,12 +231,12 @@ public abstract class AbstractCompetitorLeaderboardChart<SettingsType> extends A
|
||||
setWidget(chart);
|
||||
raceColumnNames.clear();
|
||||
|
||||
switch(selectedDetailType) {
|
||||
switch (selectedDetailType) {
|
||||
case OVERALL_RANK:
|
||||
case REGATTA_RANK:
|
||||
fillTotalRanksSeries(result, chartSeries);
|
||||
break;
|
||||
case REGATTA_TOTAL_POINTS:
|
||||
case REGATTA_TOTAL_POINTS_SUM:
|
||||
fillTotalPointsSeries(result, chartSeries);
|
||||
break;
|
||||
default:
|
||||
@@ -298,7 +298,9 @@ public abstract class AbstractCompetitorLeaderboardChart<SettingsType> extends A
|
||||
chart.setHeight(maxCompetitorCount * 30 + 100 + "px");
|
||||
}
|
||||
|
||||
private void fillTotalPointsSeries(List<com.sap.sse.common.Util.Triple<String, List<CompetitorDTO>, List<Double>>> result, List<Series> chartSeries) {
|
||||
private void fillTotalPointsSeries(
|
||||
List<com.sap.sse.common.Util.Triple<String, List<CompetitorDTO>, List<Double>>> result,
|
||||
List<Series> chartSeries) {
|
||||
Double maxTotalPoints = 0.0;
|
||||
Set<Series> unusedSeries = new HashSet<Series>(competitorSeries.values());
|
||||
for (Series series : competitorSeries.values()) {
|
||||
@@ -308,11 +310,10 @@ public abstract class AbstractCompetitorLeaderboardChart<SettingsType> extends A
|
||||
}
|
||||
int raceColumnNumber = 0;
|
||||
int maxCompetitorCount = 0;
|
||||
Map<CompetitorDTO, Double> sumTotalPointsMap = new HashMap<CompetitorDTO, Double>();
|
||||
for (com.sap.sse.common.Util.Triple<String, List<CompetitorDTO>, List<Double>> entry : result) {
|
||||
List<Double> dataValues = entry.getC();
|
||||
raceColumnNames.add(entry.getA());
|
||||
if(hasValidValues(dataValues)) {
|
||||
if (hasValidValues(dataValues)) {
|
||||
raceColumnNamesWithData.add(entry.getA());
|
||||
int index = 0;
|
||||
maxCompetitorCount = Math.max(maxCompetitorCount, entry.getB().size());
|
||||
@@ -320,18 +321,13 @@ public abstract class AbstractCompetitorLeaderboardChart<SettingsType> extends A
|
||||
if (isCompetitorVisible(competitor)) {
|
||||
Series series = getOrCreateSeries(competitor);
|
||||
Double dataValue = dataValues.get(index);
|
||||
if(dataValue != null) {
|
||||
if (dataValue != null) {
|
||||
Double sumTotalPoints = dataValue;
|
||||
if(sumTotalPointsMap.containsKey(competitor)) {
|
||||
sumTotalPoints += sumTotalPointsMap.get(competitor);
|
||||
}
|
||||
sumTotalPointsMap.put(competitor, sumTotalPoints);
|
||||
series.addPoint(raceColumnNumber, sumTotalPoints, /* redraw */ false, /* shift */ false, /* animation */ false);
|
||||
if(sumTotalPoints > maxTotalPoints) {
|
||||
if (sumTotalPoints > maxTotalPoints) {
|
||||
maxTotalPoints = sumTotalPoints;
|
||||
}
|
||||
}
|
||||
|
||||
unusedSeries.remove(series);
|
||||
if (!chartSeries.contains(series)) {
|
||||
chart.addSeries(series);
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ public class MultiCompetitorLeaderboardChartSettingsDialogComponent implements S
|
||||
|
||||
availableDetailsTypes = new ArrayList<DetailType>();
|
||||
availableDetailsTypes.add(DetailType.REGATTA_RANK);
|
||||
availableDetailsTypes.add(DetailType.REGATTA_TOTAL_POINTS);
|
||||
availableDetailsTypes.add(DetailType.REGATTA_TOTAL_POINTS_SUM);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ public class LeaderboardEntryPoint extends AbstractEntryPoint {
|
||||
String chartDetailParam = GwtHttpRequestUtils.getStringParameter(PARAM_CHART_DETAIL, null);
|
||||
DetailType chartDetailType;
|
||||
if (chartDetailParam != null && (DetailType.REGATTA_RANK.name().equals(chartDetailParam) || DetailType.OVERALL_RANK.name().equals(chartDetailParam) ||
|
||||
DetailType.REGATTA_TOTAL_POINTS.name().equals(chartDetailParam))) {
|
||||
DetailType.REGATTA_TOTAL_POINTS_SUM.name().equals(chartDetailParam))) {
|
||||
chartDetailType = DetailType.valueOf(chartDetailParam);
|
||||
} else {
|
||||
chartDetailType = leaderboardType.isMetaLeaderboard() ? DetailType.OVERALL_RANK : DetailType.REGATTA_RANK;
|
||||
|
||||
+2
-2
@@ -518,7 +518,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
|
||||
} else {
|
||||
trackedRacesIdentifiers = Collections.emptyList();
|
||||
}
|
||||
raceTimesInfoProvider = new RaceTimesInfoProvider(getSailingService(), errorReporter,
|
||||
raceTimesInfoProvider = new RaceTimesInfoProvider(getSailingService(), asyncActionsExecutor, errorReporter,
|
||||
trackedRacesIdentifiers, timer.getRefreshInterval());
|
||||
}
|
||||
return raceTimesInfoProvider;
|
||||
@@ -1336,7 +1336,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
|
||||
|
||||
@Override
|
||||
public SafeHtmlHeader getHeader() {
|
||||
return new SafeHtmlHeaderWithTooltip(SafeHtmlUtils.fromString(stringMessages.racesCompleted()), stringMessages.racesCompletedTooltip());
|
||||
return new SafeHtmlHeaderWithTooltip(SafeHtmlUtils.fromString(stringMessages.racesScored()), stringMessages.racesScoredTooltip());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -180,8 +180,8 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone
|
||||
overallDetailCheckboxes);
|
||||
overallDetailDialogContent.add(checkbox);
|
||||
}
|
||||
showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox = dialog.createCheckbox(stringMessages.showNumberOfRacesCompleted());
|
||||
dialog.addTooltip(showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox, stringMessages.showNumberOfRacesCompleted());
|
||||
showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox = dialog.createCheckbox(stringMessages.showNumberOfRacesScored());
|
||||
dialog.addTooltip(showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox, stringMessages.showNumberOfRacesScored());
|
||||
showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox.setValue(showOverallColumnWithNumberOfRacesSailedPerCompetitor);
|
||||
overallDetailDialogContent.add(showOverallColumnWithNumberOfRacesSailedPerCompetitorCheckBox);
|
||||
overallDetailDialog.add(overallDetailDialogContent);
|
||||
|
||||
+3
-3
@@ -172,12 +172,12 @@ public class LeaderboardUrlConfigurationDialog extends SettingsDialog<Leaderboar
|
||||
|
||||
chartDetailListBox = dialog.createListBox(false);
|
||||
urlSettingsContent.add(chartDetailListBox);
|
||||
if(leaderboardType.isMetaLeaderboard()) {
|
||||
if (leaderboardType.isMetaLeaderboard()) {
|
||||
chartDetailListBox.addItem(DetailType.OVERALL_RANK.name());
|
||||
chartDetailListBox.addItem(DetailType.REGATTA_TOTAL_POINTS.name());
|
||||
chartDetailListBox.addItem(DetailType.REGATTA_TOTAL_POINTS_SUM.name());
|
||||
} else {
|
||||
chartDetailListBox.addItem(DetailType.REGATTA_RANK.name());
|
||||
chartDetailListBox.addItem(DetailType.REGATTA_TOTAL_POINTS.name());
|
||||
chartDetailListBox.addItem(DetailType.REGATTA_TOTAL_POINTS_SUM.name());
|
||||
}
|
||||
chartDetailListBox.setSelectedIndex(0);
|
||||
|
||||
|
||||
+49
-12
@@ -5,7 +5,13 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.core.client.Scheduler;
|
||||
import com.google.gwt.dom.client.NativeEvent;
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.user.client.Command;
|
||||
import com.google.gwt.user.client.Event;
|
||||
import com.google.gwt.user.client.Event.NativePreviewEvent;
|
||||
import com.google.gwt.user.client.Event.NativePreviewHandler;
|
||||
import com.google.gwt.user.client.Window;
|
||||
import com.google.gwt.user.client.rpc.ServiceDefTarget;
|
||||
import com.google.gwt.user.client.ui.DockLayoutPanel;
|
||||
@@ -34,6 +40,7 @@ import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.UserDTO;
|
||||
import com.sap.sse.gwt.client.EntryPointHelper;
|
||||
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
|
||||
import com.sap.sse.gwt.client.player.Timer;
|
||||
import com.sap.sse.gwt.client.player.Timer.PlayModes;
|
||||
import com.sap.sse.gwt.client.useragent.UserAgentChecker;
|
||||
@@ -59,6 +66,8 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint {
|
||||
private final MediaServiceAsync mediaService = GWT.create(MediaService.class);
|
||||
private final UserManagementServiceAsync userManagementService = GWT.create(UserManagementService.class);
|
||||
|
||||
private boolean toolbarAndLogoAndTitleBarHidden;
|
||||
|
||||
@Override
|
||||
protected void doOnModuleLoad() {
|
||||
super.doOnModuleLoad();
|
||||
@@ -92,9 +101,10 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint {
|
||||
final boolean canReplayWhileLiveIsPossible = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, false);
|
||||
final boolean autoSelectMedia = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_AUTOSELECT_MEDIA, false);
|
||||
final boolean showMapControls = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, true /* default*/);
|
||||
raceboardViewConfig = new RaceBoardViewConfiguration(activeCompetitorsFilterSetName, showLeaderboard, showWindChart,
|
||||
showCompetitorsChart, canReplayWhileLiveIsPossible, autoSelectMedia);
|
||||
|
||||
final boolean showNavigationPanel = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_NAVIGATION_PANEL, true /* default */);
|
||||
raceboardViewConfig = new RaceBoardViewConfiguration(activeCompetitorsFilterSetName, showLeaderboard,
|
||||
showWindChart, showCompetitorsChart, canReplayWhileLiveIsPossible, autoSelectMedia, showNavigationPanel);
|
||||
|
||||
final ParallelExecutionCallback<List<String>> getLeaderboardNamesCallback = new ParallelExecutionCallback<List<String>>();
|
||||
final ParallelExecutionCallback<List<RegattaDTO>> getRegattasCallback = new ParallelExecutionCallback<List<RegattaDTO>>();
|
||||
final ParallelExecutionCallback<LeaderboardGroupDTO> getLeaderboardGroupByNameCallback = new ParallelExecutionCallback<LeaderboardGroupDTO>();
|
||||
@@ -161,11 +171,12 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint {
|
||||
List<RegattaAndRaceIdentifier> singletonList = Collections.singletonList(selectedRace.getRaceIdentifier());
|
||||
raceSelectionModel.setSelection(singletonList);
|
||||
Timer timer = new Timer(PlayModes.Replay, 1000l);
|
||||
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, this, singletonList, 5000l /* requestInterval*/);
|
||||
RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, user, timer, raceSelectionModel, leaderboardName,
|
||||
AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor();
|
||||
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, this, singletonList, 5000l /* requestInterval*/);
|
||||
RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, asyncActionsExecutor, user, timer, raceSelectionModel, leaderboardName,
|
||||
leaderboardGroupName, raceboardViewConfig, RaceBoardEntryPoint.this, stringMessages, userAgent, raceTimesInfoProvider, showMapControls);
|
||||
raceBoardPanel.fillRegattas(regattas);
|
||||
createRaceBoardInOneScreenMode(raceBoardPanel);
|
||||
createRaceBoardInOneScreenMode(raceBoardPanel, raceboardViewConfig);
|
||||
}
|
||||
|
||||
private RaceDTO findRace(String regattaName, String raceName, List<RegattaDTO> regattas) {
|
||||
@@ -217,21 +228,47 @@ public class RaceBoardEntryPoint extends AbstractEntryPoint {
|
||||
return logoAndTitlePanel;
|
||||
}
|
||||
|
||||
private void createRaceBoardInOneScreenMode(RaceBoardPanel raceBoardPanel) {
|
||||
DockLayoutPanel p = new DockLayoutPanel(Unit.PX);
|
||||
private void createRaceBoardInOneScreenMode(RaceBoardPanel raceBoardPanel,
|
||||
RaceBoardViewConfiguration raceboardViewConfiguration) {
|
||||
final DockLayoutPanel p = new DockLayoutPanel(Unit.PX);
|
||||
RootLayoutPanel.get().add(p);
|
||||
Panel toolbarPanel = raceBoardPanel.getToolbarPanel();
|
||||
final Panel toolbarPanel = raceBoardPanel.getToolbarPanel();
|
||||
if (!UserAgentChecker.INSTANCE.isUserAgentSupported(userAgent)) {
|
||||
HTML lbl = new HTML(stringMessages.warningBrowserUnsupported());
|
||||
lbl.setStyleName("browserOptimizedMessage");
|
||||
toolbarPanel.add(lbl);
|
||||
}
|
||||
FlowPanel logoAndTitlePanel = createLogoAndTitlePanel(raceBoardPanel);
|
||||
final FlowPanel logoAndTitlePanel = createLogoAndTitlePanel(raceBoardPanel);
|
||||
FlowPanel timePanel = createTimePanel(raceBoardPanel);
|
||||
p.addNorth(logoAndTitlePanel, 68);
|
||||
p.addNorth(toolbarPanel, 40);
|
||||
p.addSouth(timePanel, 90);
|
||||
toolbarAndLogoAndTitleBarHidden = false;
|
||||
if (!raceboardViewConfiguration.isShowNavigationPanel()) {
|
||||
p.setWidgetHidden(toolbarPanel, true);
|
||||
globalNavigationPanel.setVisible(false);
|
||||
toolbarAndLogoAndTitleBarHidden = true;
|
||||
}
|
||||
Event.addNativePreviewHandler(new NativePreviewHandler() {
|
||||
@Override
|
||||
public void onPreviewNativeEvent(NativePreviewEvent event) {
|
||||
NativeEvent ne = event.getNativeEvent();
|
||||
if ("keydown".equals(ne.getType()) && ne.getCtrlKey()
|
||||
&& (ne.getKeyCode() == 'm' || ne.getKeyCode() == 'M')) {
|
||||
ne.preventDefault();
|
||||
Scheduler.get().scheduleDeferred(new Command() {
|
||||
@Override
|
||||
public void execute() {
|
||||
p.setWidgetHidden(toolbarPanel, !toolbarAndLogoAndTitleBarHidden);
|
||||
globalNavigationPanel.setVisible(toolbarAndLogoAndTitleBarHidden);
|
||||
toolbarAndLogoAndTitleBarHidden = !toolbarAndLogoAndTitleBarHidden;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
p.addSouth(timePanel, 90);
|
||||
p.add(raceBoardPanel);
|
||||
p.addStyleName("dockLayoutPanel");
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -131,7 +131,7 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
|
||||
|
||||
private final static String LOCAL_STORAGE_COMPETITORS_FILTER_SETS_KEY = "sailingAnalytics.raceBoard.competitorsFilterSets";
|
||||
|
||||
public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService, UserDTO theUser, Timer timer,
|
||||
public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService, AsyncActionsExecutor asyncActionsExecutor, UserDTO theUser, Timer timer,
|
||||
RaceSelectionProvider theRaceSelectionProvider, String leaderboardName, String leaderboardGroupName,
|
||||
RaceBoardViewConfiguration raceboardViewConfiguration, ErrorReporter errorReporter, final StringMessages stringMessages,
|
||||
UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls) {
|
||||
@@ -150,7 +150,7 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
|
||||
racesByIdentifier = new HashMap<RaceIdentifier, RaceDTO>();
|
||||
selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next();
|
||||
this.setRaceBoardName(selectedRaceIdentifier.getRaceName());
|
||||
asyncActionsExecutor = new AsyncActionsExecutor();
|
||||
this.asyncActionsExecutor = asyncActionsExecutor;
|
||||
FlowPanel mainPanel = new FlowPanel();
|
||||
mainPanel.setSize("100%", "100%");
|
||||
setWidget(mainPanel);
|
||||
@@ -163,6 +163,7 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
|
||||
componentControlsPanel = new FlowPanel();
|
||||
componentControlsPanel.addStyleName("raceBoardNavigation");
|
||||
toolbarPanel.add(componentControlsPanel);
|
||||
|
||||
viewControlsPanel = new FlowPanel();
|
||||
viewControlsPanel.addStyleName("raceBoardControls");
|
||||
toolbarPanel.add(viewControlsPanel);
|
||||
|
||||
+10
-2
@@ -12,6 +12,7 @@ public class RaceBoardViewConfiguration {
|
||||
private String activeCompetitorsFilterSetName;
|
||||
private boolean canReplayDuringLiveRaces;
|
||||
private final boolean autoSelectMedia;
|
||||
private final boolean showNavigationPanel;
|
||||
|
||||
public static final String PARAM_VIEW_MODE = "viewMode";
|
||||
public static final String PARAM_VIEW_SHOW_LEADERBOARD = "viewShowLeaderboard";
|
||||
@@ -28,16 +29,19 @@ public class RaceBoardViewConfiguration {
|
||||
showWindChart = false;
|
||||
showCompetitorsChart = false;
|
||||
autoSelectMedia = false;
|
||||
showNavigationPanel = true;
|
||||
}
|
||||
|
||||
public RaceBoardViewConfiguration(String activeCompetitorsFilterSetName, boolean showLeaderboard,
|
||||
boolean showWindChart, boolean showCompetitorsChart, boolean canReplayDuringLiveRaces, boolean autoSelectMedia) {
|
||||
boolean showWindChart, boolean showCompetitorsChart, boolean canReplayDuringLiveRaces,
|
||||
boolean autoSelectMedia, boolean showNavigationPanel) {
|
||||
this.activeCompetitorsFilterSetName = activeCompetitorsFilterSetName;
|
||||
this.showLeaderboard = showLeaderboard;
|
||||
this.showWindChart = showWindChart;
|
||||
this.showCompetitorsChart = showCompetitorsChart;
|
||||
this.canReplayDuringLiveRaces = canReplayDuringLiveRaces;
|
||||
this.autoSelectMedia = autoSelectMedia;
|
||||
this.autoSelectMedia = autoSelectMedia;
|
||||
this.showNavigationPanel = showNavigationPanel;
|
||||
}
|
||||
|
||||
public boolean isShowLeaderboard() {
|
||||
@@ -64,4 +68,8 @@ public class RaceBoardViewConfiguration {
|
||||
return autoSelectMedia;
|
||||
}
|
||||
|
||||
public boolean isShowNavigationPanel() {
|
||||
return showNavigationPanel;
|
||||
}
|
||||
|
||||
}
|
||||
+10
-8
@@ -38,17 +38,19 @@ public class FlagImageResolver {
|
||||
flagImageMap.put(Flags.ZULU, resources.flagZulu());
|
||||
}
|
||||
|
||||
public ImageResource resolveFlagToImage(Flags flag) {
|
||||
return flagImageMap.get(flag);
|
||||
public ImageResource resolveFlagToImage(Flags flag, boolean isDisplayed, boolean displayStateChanged) {
|
||||
return isDisplayed || displayStateChanged ? flagImageMap.get(flag) : null;
|
||||
}
|
||||
|
||||
public ImageResource resolveFlagDirectionToImage(boolean isDisplayed) {
|
||||
ImageResource result;
|
||||
public ImageResource resolveFlagDirectionToImage(boolean isDisplayed, boolean displayStateChanged) {
|
||||
ImageResource result = null;
|
||||
|
||||
if (isDisplayed) {
|
||||
result = resources.arrowUp();
|
||||
} else {
|
||||
result = resources.arrowDown();
|
||||
if (displayStateChanged) {
|
||||
if (isDisplayed) {
|
||||
result = resources.arrowUp();
|
||||
} else {
|
||||
result = resources.arrowDown();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
+15
-4
@@ -31,7 +31,7 @@ public class RaceDetailPanel extends SimplePanel {
|
||||
d = -d;
|
||||
}
|
||||
NumberFormat decimalFormat = NumberFormat.getFormat("##.###");
|
||||
return "" + d + '\u00B0' + m + '\u2032' + decimalFormat.format(s) + '\u2033'; // add º, ', " symbols
|
||||
return "" + d + '\u00B0' + m + '\u2032' + decimalFormat.format(s) + '\u2033'; // add �, ', " symbols
|
||||
}
|
||||
|
||||
private static final int MAX_PROCEDURE_FIELDS = 2;
|
||||
@@ -50,7 +50,9 @@ public class RaceDetailPanel extends SimplePanel {
|
||||
private final Label finishTimeLabel = new Label();
|
||||
private final Label finishDurationLabel = new Label();
|
||||
private final Label protestTimeLabel = new Label();
|
||||
private final Label protestDateLabel = new Label();
|
||||
private final Label updateTimeLabel = new Label();
|
||||
private final Label updateDateLabel = new Label();
|
||||
private final Label vesselPositionLabel = new Label();
|
||||
private final Label windLabel = new Label();
|
||||
private final Label startProcedureLabel = new Label();
|
||||
@@ -94,9 +96,9 @@ public class RaceDetailPanel extends SimplePanel {
|
||||
basicsGrid.setWidget(2, 1, protestTimeLabel);
|
||||
basicsGrid.setWidget(0, 2, startDateLabel);
|
||||
basicsGrid.setWidget(1, 2, finishDurationLabel);
|
||||
basicsGrid.setWidget(2, 2, new Label(""));
|
||||
basicsGrid.setWidget(2, 2, protestDateLabel);
|
||||
|
||||
Grid managementGrid = new Grid(3, 2);
|
||||
Grid managementGrid = new Grid(3, 3);
|
||||
managementGrid.setCellSpacing(cellSpacing);
|
||||
managementGrid.setWidget(0, 0, new Label("Last update at"));
|
||||
managementGrid.setWidget(1, 0, new Label("Vessel position"));
|
||||
@@ -104,6 +106,7 @@ public class RaceDetailPanel extends SimplePanel {
|
||||
managementGrid.setWidget(0, 1, updateTimeLabel);
|
||||
managementGrid.setWidget(1, 1, vesselPositionLabel);
|
||||
managementGrid.setWidget(2, 1, windLabel);
|
||||
managementGrid.setWidget(0, 2, updateDateLabel);
|
||||
|
||||
procedureGrid = new FlexTable();
|
||||
procedureGrid.setCellSpacing(cellSpacing);
|
||||
@@ -138,7 +141,7 @@ public class RaceDetailPanel extends SimplePanel {
|
||||
return;
|
||||
}
|
||||
String statusText = flagInterpreter.getMeaningOfRaceStateAndFlags(data.raceInfo.lastStatus,
|
||||
data.raceInfo.lastUpperFlag, data.raceInfo.lastLowerFlag, data.raceInfo.isLastFlagDisplayed);
|
||||
data.raceInfo.lastUpperFlag, data.raceInfo.lastLowerFlag, data.raceInfo.lastFlagsAreDisplayed);
|
||||
String headerLabelText = data.regattaDisplayName + " " + data.raceInfo.fleetName + " " + data.raceInfo.raceName
|
||||
+ " (" + statusText + ")";
|
||||
headerLabel.setText(stringMessages.showingDetailsOfRace(headerLabelText));
|
||||
@@ -166,10 +169,18 @@ public class RaceDetailPanel extends SimplePanel {
|
||||
.format(data.raceInfo.protestFinishTime);
|
||||
protestTimeLabel.setText(protestTimeText);
|
||||
|
||||
String protestDateText = data.raceInfo.protestFinishTime == null ? "" : dateFormatter
|
||||
.format(data.raceInfo.protestFinishTime);
|
||||
protestDateLabel.setText(protestDateText);
|
||||
|
||||
String updateTimeText = data.raceInfo.lastUpdateTime == null ? "-" : timeFormatter
|
||||
.format(data.raceInfo.lastUpdateTime);
|
||||
updateTimeLabel.setText(updateTimeText);
|
||||
|
||||
String updateDateText = data.raceInfo.lastUpdateTime == null ? "" : dateFormatter
|
||||
.format(data.raceInfo.lastUpdateTime);
|
||||
updateDateLabel.setText(updateDateText);
|
||||
|
||||
String vesselPositionText = stringMessages.unknown();
|
||||
String windText = stringMessages.unknown();
|
||||
if (data.raceInfo.lastWind != null) {
|
||||
|
||||
+18
-10
@@ -186,27 +186,30 @@ public class RegattaOverviewPanel extends SimplePanel {
|
||||
flexTable.setWidget(0, 0, eventVenueGrid);
|
||||
flexTable.getCellFormatter().setVerticalAlignment(0, 0, HasVerticalAlignment.ALIGN_MIDDLE);
|
||||
|
||||
boolean showLeaderboardButton = Window.Location.getParameter("enableLeaderboard") != null
|
||||
final boolean showLeaderboardButton = Window.Location.getParameter("enableLeaderboard") != null
|
||||
&& Window.Location.getParameter("enableLeaderboard").equalsIgnoreCase("true");
|
||||
|
||||
if (showLeaderboardButton) {
|
||||
leaderboardCheckBox = addLeaderboardEnablerButton();
|
||||
leaderboardCheckBox.getElement().getStyle().setMarginLeft(20.0, Unit.PX);
|
||||
leaderboardsTabPanel = new TabPanel();
|
||||
leaderboardsTabPanel.setStyleName("RegattaOverview-Leaderboards");
|
||||
mainPanel.add(leaderboardsTabPanel);
|
||||
} else {
|
||||
leaderboardCheckBox = null;
|
||||
leaderboardsTabPanel = null;
|
||||
}
|
||||
|
||||
|
||||
HorizontalPanel refreshStartStopClockPanel = getRefreshStartStopClockPanel();
|
||||
|
||||
flexTable.setWidget(0, 1, refreshStartStopClockPanel);
|
||||
|
||||
mainPanel.add(flexTable);
|
||||
mainPanel.add(regattaRaceStatesComponent);
|
||||
|
||||
if (showLeaderboardButton) {
|
||||
leaderboardsTabPanel = new TabPanel();
|
||||
leaderboardsTabPanel.setStyleName("RegattaOverview-Leaderboards");
|
||||
leaderboardsTabPanel.setVisible(false);
|
||||
mainPanel.add(leaderboardsTabPanel);
|
||||
} else {
|
||||
leaderboardsTabPanel = null;
|
||||
}
|
||||
|
||||
onUpdateUI(uiUpdateTimer.getLiveTimePointAsDate());
|
||||
}
|
||||
|
||||
@@ -254,8 +257,13 @@ public class RegattaOverviewPanel extends SimplePanel {
|
||||
errorReporter, stringMessages, userAgent, /*showRaceDetails*/false);
|
||||
leaderboardsTabPanel.add(leaderboardPanel, leaderboard.getDisplayName() + " " + stringMessages.leaderboard());
|
||||
}
|
||||
leaderboardsTabPanel.setVisible(true);
|
||||
leaderboardsTabPanel.selectTab(0);
|
||||
if (!result.isEmpty()) {
|
||||
leaderboardsTabPanel.setVisible(true);
|
||||
leaderboardsTabPanel.selectTab(0);
|
||||
} else {
|
||||
leaderboardCheckBox.setValue(false);
|
||||
errorReporter.reportError("Error trying to load leaderboard. Event could not be associated to Regatta.");
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
|
||||
+25
-15
@@ -16,6 +16,7 @@ import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.i18n.client.DateTimeFormat;
|
||||
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
|
||||
import com.google.gwt.json.client.JSONObject;
|
||||
import com.google.gwt.json.client.JSONParser;
|
||||
import com.google.gwt.json.client.JSONValue;
|
||||
@@ -38,6 +39,7 @@ import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.SimplePanel;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.google.gwt.user.datepicker.client.CalendarUtil;
|
||||
import com.google.gwt.view.client.ListDataProvider;
|
||||
import com.sap.sailing.domain.common.LeaderboardNameConstants;
|
||||
import com.sap.sailing.domain.common.PassingInstruction;
|
||||
@@ -83,9 +85,11 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
|
||||
private ListDataProvider<RegattaOverviewEntryDTO> regattaOverviewDataProvider;
|
||||
private final VerticalPanel mainPanel;
|
||||
private final DateTimeFormat timeFormatter = DateTimeFormat.getFormat("HH:mm:ss");
|
||||
private final DateTimeFormat dateFormatter = DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT);
|
||||
|
||||
private final SailingServiceAsync sailingService;
|
||||
private final StringMessages stringMessages;
|
||||
private final ErrorReporter errorReporter;
|
||||
private final UUID eventId;
|
||||
|
||||
private EventDTO eventDTO;
|
||||
@@ -126,6 +130,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
|
||||
final StringMessages stringMessages, final UUID eventId, RegattaRaceStatesSettings settings, Timer timerToSynchronize) {
|
||||
this.sailingService = sailingService;
|
||||
this.stringMessages = stringMessages;
|
||||
this.errorReporter = errorReporter;
|
||||
this.eventId = eventId;
|
||||
this.allEntries = new ArrayList<RegattaOverviewEntryDTO>();
|
||||
this.timerToSynchronize = timerToSynchronize;
|
||||
@@ -190,7 +195,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
|
||||
new AsyncCallback<List<RegattaOverviewEntryDTO>>() {
|
||||
@Override
|
||||
public void onFailure(Throwable cause) {
|
||||
|
||||
errorReporter.reportError(cause.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -207,6 +212,18 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private String formatTime(Date time) {
|
||||
String result = "-";
|
||||
if (time != null) {
|
||||
result = "";
|
||||
if (! CalendarUtil.isSameDate(time, new Date())) {
|
||||
result += dateFormatter.format(time) + " ";
|
||||
}
|
||||
result += timeFormatter.format(time);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private CellTable<RegattaOverviewEntryDTO> createRegattaTable() {
|
||||
CellTable<RegattaOverviewEntryDTO> table = new CellTable<RegattaOverviewEntryDTO>(/* pageSize */10000, tableRes);
|
||||
@@ -341,11 +358,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
|
||||
TextColumn<RegattaOverviewEntryDTO> raceStartTimeColumn = new TextColumn<RegattaOverviewEntryDTO>() {
|
||||
@Override
|
||||
public String getValue(RegattaOverviewEntryDTO entryDTO) {
|
||||
String result = "-";
|
||||
if (entryDTO.raceInfo.startTime != null) {
|
||||
result = timeFormatter.format(entryDTO.raceInfo.startTime);
|
||||
}
|
||||
return result;
|
||||
return formatTime(entryDTO.raceInfo.startTime);
|
||||
}
|
||||
};
|
||||
raceStartTimeColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
|
||||
@@ -375,7 +388,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
|
||||
@Override
|
||||
public SafeHtml getValue(final RegattaOverviewEntryDTO entryDTO) {
|
||||
String status = flagInterpreter.getMeaningOfRaceStateAndFlags(entryDTO.raceInfo.lastStatus, entryDTO.raceInfo.lastUpperFlag,
|
||||
entryDTO.raceInfo.lastLowerFlag, entryDTO.raceInfo.isLastFlagDisplayed);
|
||||
entryDTO.raceInfo.lastLowerFlag, entryDTO.raceInfo.lastFlagsAreDisplayed);
|
||||
SafeHtmlBuilder sb = new SafeHtmlBuilder();
|
||||
sb.appendHtmlConstant("<a class=\"pointeredLink\">");
|
||||
sb.appendEscaped(status);
|
||||
@@ -444,12 +457,13 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
|
||||
tooltip += " " + FlagsMeaningExplanator.getFlagsMeaning(stringMessages,
|
||||
entryDTO.raceInfo.lastUpperFlag,
|
||||
entryDTO.raceInfo.lastLowerFlag,
|
||||
entryDTO.raceInfo.isLastFlagDisplayed);
|
||||
entryDTO.raceInfo.lastFlagsAreDisplayed);
|
||||
}
|
||||
return SailingFlagsBuilder.render(
|
||||
entryDTO.raceInfo.lastUpperFlag,
|
||||
entryDTO.raceInfo.lastLowerFlag,
|
||||
entryDTO.raceInfo.isLastFlagDisplayed,
|
||||
entryDTO.raceInfo.lastFlagsAreDisplayed,
|
||||
entryDTO.raceInfo.lastFlagsDisplayedStateChanged,
|
||||
tooltip);
|
||||
}
|
||||
|
||||
@@ -459,11 +473,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
|
||||
TextColumn<RegattaOverviewEntryDTO> lastUpdateColumn = new TextColumn<RegattaOverviewEntryDTO>() {
|
||||
@Override
|
||||
public String getValue(RegattaOverviewEntryDTO entryDTO) {
|
||||
String lastUpdateTime = "-";
|
||||
if (entryDTO.raceInfo.lastUpdateTime != null) {
|
||||
lastUpdateTime = timeFormatter.format(entryDTO.raceInfo.lastUpdateTime);
|
||||
}
|
||||
return lastUpdateTime;
|
||||
return formatTime(entryDTO.raceInfo.lastUpdateTime);
|
||||
}
|
||||
};
|
||||
lastUpdateColumn.setSortable(true);
|
||||
@@ -486,7 +496,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
|
||||
} else if (entryDTO.raceInfo.lastLowerFlag != null && entryDTO.raceInfo.lastLowerFlag.equals(Flags.HOTEL)) {
|
||||
additionalInformation.append(stringMessages.furtherSignalsAshore());
|
||||
isInfoBefore = true;
|
||||
} else if (entryDTO.raceInfo.lastUpperFlag != null && entryDTO.raceInfo.lastUpperFlag.equals(Flags.XRAY) && entryDTO.raceInfo.isLastFlagDisplayed) {
|
||||
} else if (entryDTO.raceInfo.lastUpperFlag != null && entryDTO.raceInfo.lastUpperFlag.equals(Flags.XRAY) && entryDTO.raceInfo.lastFlagsAreDisplayed) {
|
||||
additionalInformation.append(stringMessages.earlyStarters());
|
||||
isInfoBefore = true;
|
||||
}
|
||||
|
||||
+5
-5
@@ -40,15 +40,15 @@ public class SailingFlagsBuilder {
|
||||
return SafeStylesUtils.fromTrustedString("float:left;padding-right:10px;");
|
||||
}
|
||||
|
||||
public static SafeHtml render(Flags upperFlag, Flags lowerFlag, boolean isDisplayed, String tooltip) {
|
||||
public static SafeHtml render(Flags upperFlag, Flags lowerFlag, boolean isDisplayed, boolean displayStateChanged, String tooltip) {
|
||||
FlagImageResolver flagImageResolver = new FlagImageResolver();
|
||||
ImageResource upperFlagImage = flagImageResolver.resolveFlagToImage(upperFlag);
|
||||
ImageResource lowerFlagImage = flagImageResolver.resolveFlagToImage(lowerFlag);
|
||||
ImageResource directionImage = flagImageResolver.resolveFlagDirectionToImage(isDisplayed);
|
||||
ImageResource upperFlagImage = flagImageResolver.resolveFlagToImage(upperFlag, isDisplayed, displayStateChanged);
|
||||
ImageResource lowerFlagImage = flagImageResolver.resolveFlagToImage(lowerFlag, isDisplayed, displayStateChanged);
|
||||
ImageResource directionImage = flagImageResolver.resolveFlagDirectionToImage(isDisplayed, displayStateChanged);
|
||||
|
||||
SafeHtml upperFlagHtml = (upperFlagImage != null) ? makeImagePrototype(upperFlagImage).getSafeHtml() : SafeHtmlUtils.EMPTY_SAFE_HTML;
|
||||
SafeHtml lowerFlagHtml = (lowerFlagImage != null) ? makeImagePrototype(lowerFlagImage).getSafeHtml() : SafeHtmlUtils.EMPTY_SAFE_HTML;
|
||||
SafeHtml directionHtml = (upperFlagImage != null) ? makeImagePrototype(directionImage).getSafeHtml() : SafeHtmlUtils.EMPTY_SAFE_HTML;
|
||||
SafeHtml directionHtml = (directionImage != null) ? makeImagePrototype(directionImage).getSafeHtml() : SafeHtmlUtils.EMPTY_SAFE_HTML;
|
||||
|
||||
SafeHtmlBuilder builder = new SafeHtmlBuilder();
|
||||
SafeStyles imgStyle = getImageStyle();
|
||||
|
||||
+103
-57
@@ -67,6 +67,7 @@ import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.LeaderboardGroupBase;
|
||||
import com.sap.sailing.domain.base.LeaderboardSearchResult;
|
||||
import com.sap.sailing.domain.base.LeaderboardSearchResultBase;
|
||||
import com.sap.sailing.domain.base.Leg;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.Nationality;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
@@ -142,8 +143,6 @@ import com.sap.sailing.domain.common.dto.FullLeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.IncrementalLeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.IncrementalOrFullLeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
|
||||
import com.sap.sailing.domain.common.dto.PositionDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO;
|
||||
@@ -382,6 +381,7 @@ import com.sap.sailing.util.BuildVersion;
|
||||
import com.sap.sailing.xrr.resultimport.schema.RegattaResults;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.common.Util.Triple;
|
||||
import com.sap.sse.common.search.KeywordQuery;
|
||||
import com.sap.sse.common.search.Result;
|
||||
|
||||
@@ -397,6 +397,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
|
||||
private final ServiceTracker<ReplicationService, ReplicationService> replicationServiceTracker;
|
||||
|
||||
private final ServiceTracker<ResultUrlRegistry, ResultUrlRegistry> resultUrlRegistryServiceTracker;
|
||||
|
||||
private final ServiceTracker<ScoreCorrectionProvider, ScoreCorrectionProvider> scoreCorrectionProviderServiceTracker;
|
||||
|
||||
private final MongoObjectFactory mongoObjectFactory;
|
||||
@@ -451,6 +453,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
context = Activator.getDefault();
|
||||
racingEventServiceTracker = createAndOpenRacingEventServiceTracker(context);
|
||||
replicationServiceTracker = createAndOpenReplicationServiceTracker(context);
|
||||
resultUrlRegistryServiceTracker = createAndOpenResultUrlRegistryServiceTracker(context);
|
||||
swissTimingAdapterTracker = createAndOpenSwissTimingAdapterTracker(context);
|
||||
tractracAdapterTracker = createAndOpenTracTracAdapterTracker(context);
|
||||
raceLogTrackingAdapterTracker = createAndOpenRaceLogTrackingAdapterTracker(context);
|
||||
@@ -556,6 +559,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
return result;
|
||||
}
|
||||
|
||||
protected ServiceTracker<ResultUrlRegistry, ResultUrlRegistry> createAndOpenResultUrlRegistryServiceTracker(
|
||||
BundleContext context) {
|
||||
ServiceTracker<ResultUrlRegistry, ResultUrlRegistry> result = new ServiceTracker<ResultUrlRegistry, ResultUrlRegistry>(
|
||||
context, ResultUrlRegistry.class.getName(), null);
|
||||
result.open();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks the OSGi system for registered score correction provider services
|
||||
*/
|
||||
@@ -820,11 +831,16 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
if (startTime != null) {
|
||||
FlagPoleState activeFlagState = state.getRacingProcedure().getActiveFlags(startTime, MillisecondsTimePoint.now());
|
||||
List<FlagPole> activeFlags = activeFlagState.getCurrentState();
|
||||
FlagPoleState previousFlagState = activeFlagState.getPreviousState(state.getRacingProcedure(), startTime);
|
||||
List<FlagPole> previousFlags = previousFlagState.getCurrentState();
|
||||
FlagPole mostInterestingFlagPole = FlagPoleState.getMostInterestingFlagPole(previousFlags, activeFlags);
|
||||
|
||||
// TODO: adapt the LastFlagFinder#getMostRecent method!
|
||||
if (!activeFlags.isEmpty()) {
|
||||
raceInfoDTO.lastUpperFlag = activeFlags.get(0).getUpperFlag();
|
||||
raceInfoDTO.lastLowerFlag = activeFlags.get(0).getLowerFlag();
|
||||
raceInfoDTO.isLastFlagDisplayed = activeFlags.get(0).isDisplayed();
|
||||
if (mostInterestingFlagPole != null) {
|
||||
raceInfoDTO.lastUpperFlag = mostInterestingFlagPole.getUpperFlag();
|
||||
raceInfoDTO.lastLowerFlag = mostInterestingFlagPole.getLowerFlag();
|
||||
raceInfoDTO.lastFlagsAreDisplayed = mostInterestingFlagPole.isDisplayed();
|
||||
raceInfoDTO.lastFlagsDisplayedStateChanged = previousFlagState.hasPoleChanged(mostInterestingFlagPole);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,7 +853,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
if (raceInfoDTO.lastStatus.equals(RaceLogRaceStatus.UNSCHEDULED)) {
|
||||
raceInfoDTO.lastUpperFlag = abortingFlagEvent.getUpperFlag();
|
||||
raceInfoDTO.lastLowerFlag = abortingFlagEvent.getLowerFlag();
|
||||
raceInfoDTO.isLastFlagDisplayed = abortingFlagEvent.isDisplayed();
|
||||
raceInfoDTO.lastFlagsAreDisplayed = abortingFlagEvent.isDisplayed();
|
||||
raceInfoDTO.lastFlagsDisplayedStateChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -854,7 +871,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
raceInfoDTO.protestFinishTime = protestStartTime.plus(protestDuration).asDate();
|
||||
raceInfoDTO.lastUpperFlag = Flags.BRAVO;
|
||||
raceInfoDTO.lastLowerFlag = Flags.NONE;
|
||||
raceInfoDTO.isLastFlagDisplayed = true;
|
||||
raceInfoDTO.lastFlagsAreDisplayed = true;
|
||||
raceInfoDTO.lastFlagsDisplayedStateChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -873,7 +891,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
raceInfoDTO.isTracked = raceColumn.getTrackedRace(fleet) != null ? true : false;
|
||||
|
||||
return raceInfoDTO;
|
||||
}
|
||||
}
|
||||
|
||||
private void fillStartProcedureSpecifics(RaceInfoDTO raceInfoDTO, ReadonlyRaceState state) {
|
||||
RaceInfoExtensionDTO info = null;
|
||||
@@ -1388,12 +1406,18 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
public CompactRaceMapDataDTO getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date,
|
||||
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
|
||||
boolean extrapolate) throws NoWindException {
|
||||
return new CompactRaceMapDataDTO(getBoatPositions(raceIdentifier, fromPerCompetitorIdAsString,
|
||||
toPerCompetitorIdAsString, extrapolate), getCoursePositions(raceIdentifier, date), getCourseSidelines(raceIdentifier, date), getQuickRanks(
|
||||
raceIdentifier, date));
|
||||
final Map<CompetitorDTO, List<GPSFixDTO>> boatPositions = getBoatPositions(raceIdentifier,
|
||||
fromPerCompetitorIdAsString, toPerCompetitorIdAsString, extrapolate);
|
||||
final CoursePositionsDTO coursePositions = getCoursePositions(raceIdentifier, date);
|
||||
final List<SidelineDTO> courseSidelines = getCourseSidelines(raceIdentifier, date);
|
||||
final List<QuickRankDTO> quickRanks = getQuickRanks(raceIdentifier, date);
|
||||
return new CompactRaceMapDataDTO(boatPositions, coursePositions, courseSidelines, quickRanks);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link LegType}s are cached within the method with a resolution of one minute. The cache key is a pair of
|
||||
* {@link TrackedLegOfCompetitor} and {@link TimePoint}.
|
||||
*
|
||||
* @param from
|
||||
* for the list of competitors provided as keys of this map, requests the GPS fixes starting with the
|
||||
* date provided as value
|
||||
@@ -1410,7 +1434,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
*/
|
||||
private Map<CompetitorDTO, List<GPSFixDTO>> getBoatPositions(RegattaAndRaceIdentifier raceIdentifier,
|
||||
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
|
||||
boolean extrapolate) throws NoWindException {
|
||||
boolean extrapolate)
|
||||
throws NoWindException {
|
||||
Map<Pair<Leg, TimePoint>, LegType> legTypeCache = new HashMap<>();
|
||||
Map<CompetitorDTO, List<GPSFixDTO>> result = new HashMap<CompetitorDTO, List<GPSFixDTO>>();
|
||||
TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
|
||||
if (trackedRace != null) {
|
||||
@@ -1460,19 +1486,28 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
GPSFixMoving fix = fixIter.next();
|
||||
while (fix != null && (fix.getTimePoint().compareTo(toTimePointExcluding) < 0 ||
|
||||
(fix.getTimePoint().equals(toTimePointExcluding) && toTimePointExcluding.equals(fromTimePoint)))) {
|
||||
Tack tack;
|
||||
try {
|
||||
tack = trackedRace.getTack(competitor, fix.getTimePoint());
|
||||
} catch (NoWindException nwe) {
|
||||
tack = null;
|
||||
}
|
||||
Wind wind = trackedRace.getWind(fix.getPosition(), toTimePointExcluding);
|
||||
final SpeedWithBearing estimatedSpeed = track.getEstimatedSpeed(fix.getTimePoint());
|
||||
Tack tack = wind == null? null : trackedRace.getTack(estimatedSpeed, wind, fix.getTimePoint());
|
||||
TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor,
|
||||
fix.getTimePoint());
|
||||
LegType legType = trackedLegOfCompetitor == null ? null : trackedRace.getTrackedLeg(
|
||||
trackedLegOfCompetitor.getLeg()).getLegType(fix.getTimePoint());
|
||||
Wind wind = trackedRace.getWind(fix.getPosition(),toTimePointExcluding);
|
||||
LegType legType;
|
||||
if (trackedLegOfCompetitor != null && trackedLegOfCompetitor.getLeg() != null) {
|
||||
TimePoint quantifiedTimePoint = quantifyTimePointWithResolution(fix.getTimePoint(), /* resolutionInMilliseconds */
|
||||
60000);
|
||||
Pair<Leg, TimePoint> cacheKey = new Pair<Leg, TimePoint>(
|
||||
trackedLegOfCompetitor.getLeg(), quantifiedTimePoint);
|
||||
legType = legTypeCache.get(cacheKey);
|
||||
if (legType == null) {
|
||||
legType = trackedRace.getTrackedLeg(trackedLegOfCompetitor.getLeg()).getLegType(
|
||||
fix.getTimePoint());
|
||||
legTypeCache.put(cacheKey, legType);
|
||||
}
|
||||
} else {
|
||||
legType = null;
|
||||
}
|
||||
WindDTO windDTO = wind == null ? null : createWindDTOFromAlreadyAveraged(wind, toTimePointExcluding);
|
||||
GPSFixDTO fixDTO = createGPSFixDTO(fix, track.getEstimatedSpeed(fix.getTimePoint()), windDTO, tack, legType, /* extrapolate */
|
||||
GPSFixDTO fixDTO = createGPSFixDTO(fix, estimatedSpeed, windDTO, tack, legType, /* extrapolate */
|
||||
false);
|
||||
fixesForCompetitor.add(fixDTO);
|
||||
if (fixIter.hasNext()) {
|
||||
@@ -1481,22 +1516,31 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
// check if fix was at date and if extrapolation is requested
|
||||
if (!fix.getTimePoint().equals(toTimePointExcluding) && extrapolate) {
|
||||
Position position = track.getEstimatedPosition(toTimePointExcluding, extrapolate);
|
||||
Tack tack2;
|
||||
try {
|
||||
tack2 = trackedRace.getTack(competitor, toTimePointExcluding);
|
||||
} catch (NoWindException nwe) {
|
||||
tack2 = null;
|
||||
}
|
||||
LegType legType2 = trackedLegOfCompetitor == null ? null : trackedRace
|
||||
.getTrackedLeg(trackedLegOfCompetitor.getLeg()).getLegType(
|
||||
fix.getTimePoint());
|
||||
SpeedWithBearing speedWithBearing = track.getEstimatedSpeed(toTimePointExcluding);
|
||||
Wind wind2 = trackedRace.getWind(position, toTimePointExcluding);
|
||||
SpeedWithBearing estimatedSpeed2 = track.getEstimatedSpeed(toTimePointExcluding);
|
||||
Tack tack2 = wind2 == null ? null : trackedRace.getTack(estimatedSpeed2, wind2, toTimePointExcluding);
|
||||
LegType legType2;
|
||||
if (trackedLegOfCompetitor != null && trackedLegOfCompetitor.getLeg() != null) {
|
||||
TimePoint quantifiedTimePoint = quantifyTimePointWithResolution(
|
||||
fix.getTimePoint(), /* resolutionInMilliseconds */
|
||||
60000);
|
||||
Pair<Leg, TimePoint> cacheKey = new Pair<Leg, TimePoint>(
|
||||
trackedLegOfCompetitor.getLeg(), quantifiedTimePoint);
|
||||
legType2 = legTypeCache.get(cacheKey);
|
||||
if (legType2 == null) {
|
||||
legType2 = trackedRace.getTrackedLeg(trackedLegOfCompetitor.getLeg())
|
||||
.getLegType(
|
||||
fix.getTimePoint());
|
||||
legTypeCache.put(cacheKey, legType2);
|
||||
}
|
||||
} else {
|
||||
legType2 = null;
|
||||
}
|
||||
WindDTO windDTO2 = wind2 == null ? null : createWindDTOFromAlreadyAveraged(wind2, toTimePointExcluding);
|
||||
GPSFixDTO extrapolated = new GPSFixDTO(
|
||||
toPerCompetitorIdAsString.get(competitorDTO.getIdAsString()),
|
||||
position==null?null:new PositionDTO(position.getLatDeg(), position.getLngDeg()),
|
||||
speedWithBearing==null?null:createSpeedWithBearingDTO(speedWithBearing), windDTO2,
|
||||
estimatedSpeed2==null?null:createSpeedWithBearingDTO(estimatedSpeed2), windDTO2,
|
||||
tack2, legType2, /* extrapolated */ true);
|
||||
fixesForCompetitor.add(extrapolated);
|
||||
}
|
||||
@@ -1510,6 +1554,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
return result;
|
||||
}
|
||||
|
||||
private TimePoint quantifyTimePointWithResolution(TimePoint timePoint, long resolutionInMilliseconds) {
|
||||
return new MillisecondsTimePoint(timePoint.asMillis() / resolutionInMilliseconds * resolutionInMilliseconds);
|
||||
}
|
||||
|
||||
private SpeedWithBearingDTO createSpeedWithBearingDTO(SpeedWithBearing speedWithBearing) {
|
||||
return new SpeedWithBearingDTO(speedWithBearing.getKnots(), speedWithBearing
|
||||
.getBearing().getDegrees());
|
||||
@@ -2563,18 +2611,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
return result;
|
||||
}
|
||||
|
||||
private com.sap.sse.common.Util.Triple<String, List<CompetitorDTO>, List<Double>> getTotalPointsForRaceColumn(LeaderboardDTO leaderboard,
|
||||
RaceColumnDTO raceColumn) throws NoWindException {
|
||||
List<Double> values = new ArrayList<Double>();
|
||||
List<CompetitorDTO> competitorDTOs = new ArrayList<CompetitorDTO>();
|
||||
for (Entry<CompetitorDTO, LeaderboardRowDTO> entry : leaderboard.rows.entrySet()) {
|
||||
LeaderboardEntryDTO leaderboardEntryDTO = entry.getValue().fieldsByRaceColumnName.get(raceColumn.getName());
|
||||
values.add(leaderboardEntryDTO != null ? leaderboardEntryDTO.totalPoints : null);
|
||||
competitorDTOs.add(entry.getKey());
|
||||
}
|
||||
return new com.sap.sse.common.Util.Triple<String, List<CompetitorDTO>, List<Double>>(raceColumn.getName(), competitorDTOs, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<com.sap.sse.common.Util.Triple<String, List<CompetitorDTO>, List<Double>>> getLeaderboardDataEntriesForAllRaceColumns(String leaderboardName,
|
||||
Date date, DetailType detailType) throws Exception {
|
||||
@@ -2589,19 +2625,22 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
} else {
|
||||
timePoint = new MillisecondsTimePoint(date);
|
||||
}
|
||||
TimePoint effectiveTimePoint = timePoint == null ? leaderboard.getNowMinusDelay() : timePoint;
|
||||
if (detailType != null) {
|
||||
switch (detailType) {
|
||||
case REGATTA_TOTAL_POINTS:
|
||||
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint, Collections.<String> emptyList(),
|
||||
/* addOverallDetails */ false, getService(), baseDomainFactory);
|
||||
for (RaceColumnDTO raceColumnDTO : leaderboardDTO.getRaceList()) {
|
||||
result.add(getTotalPointsForRaceColumn(leaderboardDTO, raceColumnDTO));
|
||||
case REGATTA_TOTAL_POINTS_SUM:
|
||||
for (Entry<RaceColumn, Map<Competitor, Double>> e : leaderboard.getTotalPointsSumAfterRaceColumn(effectiveTimePoint).entrySet()) {
|
||||
List<CompetitorDTO> competitorDTOs = new ArrayList<>();
|
||||
List<Double> pointSums = new ArrayList<>();
|
||||
for (Entry<Competitor, Double> e2 : e.getValue().entrySet()) {
|
||||
competitorDTOs.add(baseDomainFactory.convertToCompetitorDTO(e2.getKey()));
|
||||
pointSums.add(e2.getValue());
|
||||
}
|
||||
result.add(new Triple<String, List<CompetitorDTO>, List<Double>>(e.getKey().getName(), competitorDTOs, pointSums));
|
||||
}
|
||||
break;
|
||||
case REGATTA_RANK:
|
||||
case OVERALL_RANK:
|
||||
TimePoint effectiveTimePoint = timePoint == null ?
|
||||
leaderboard.getNowMinusDelay() : timePoint;
|
||||
Map<RaceColumn, List<Competitor>> competitorsFromBestToWorst = leaderboard
|
||||
.getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(effectiveTimePoint);
|
||||
for (Entry<RaceColumn, List<Competitor>> e : competitorsFromBestToWorst.entrySet()) {
|
||||
@@ -2613,7 +2652,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
competitorDTOs.add(baseDomainFactory.convertToCompetitorDTO(competitor));
|
||||
rank++;
|
||||
}
|
||||
result.add(new com.sap.sse.common.Util.Triple<String, List<CompetitorDTO>, List<Double>>(e.getKey().getName(), competitorDTOs, values));
|
||||
result.add(new Triple<String, List<CompetitorDTO>, List<Double>>(e.getKey().getName(), competitorDTOs, values));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -3397,8 +3436,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
public List<String> getResultImportUrls(String resultProviderName) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
ResultUrlProvider urlBasedScoreCorrectionProvider = getUrlBasedScoreCorrectionProvider(resultProviderName);
|
||||
ResultUrlRegistry resultUrlRegistry = getResultUrlRegistry();
|
||||
if (urlBasedScoreCorrectionProvider != null) {
|
||||
Iterable<URL> allUrls = ResultUrlRegistry.INSTANCE.getResultUrls(resultProviderName);
|
||||
Iterable<URL> allUrls = resultUrlRegistry.getResultUrls(resultProviderName);
|
||||
for (URL url : allUrls) {
|
||||
result.add(url.toString());
|
||||
}
|
||||
@@ -3409,9 +3449,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
@Override
|
||||
public void removeResultImportURLs(String resultProviderName, Set<String> toRemove) throws Exception {
|
||||
ResultUrlProvider urlBasedScoreCorrectionProvider = getUrlBasedScoreCorrectionProvider(resultProviderName);
|
||||
ResultUrlRegistry resultUrlRegistry = getResultUrlRegistry();
|
||||
if (urlBasedScoreCorrectionProvider != null) {
|
||||
for (String urlToRemove : toRemove) {
|
||||
ResultUrlRegistry.INSTANCE.unregisterResultUrl(resultProviderName, new URL(urlToRemove));
|
||||
resultUrlRegistry.unregisterResultUrl(resultProviderName, new URL(urlToRemove));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3420,8 +3461,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
public void addResultImportUrl(String resultProviderName, String url) throws Exception {
|
||||
ResultUrlProvider urlBasedScoreCorrectionProvider = getUrlBasedScoreCorrectionProvider(resultProviderName);
|
||||
if (urlBasedScoreCorrectionProvider != null) {
|
||||
ResultUrlRegistry.INSTANCE.registerResultUrl(resultProviderName, new URL(url));
|
||||
ResultUrlRegistry resultUrlRegistry = getResultUrlRegistry();
|
||||
resultUrlRegistry.registerResultUrl(resultProviderName, new URL(url));
|
||||
}
|
||||
}
|
||||
|
||||
private ResultUrlRegistry getResultUrlRegistry() {
|
||||
return resultUrlRegistryServiceTracker.getService();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+35
-109
@@ -14,6 +14,7 @@ import java.util.logging.Logger;
|
||||
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.Duration;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
@@ -42,6 +43,7 @@ import com.sap.sailing.gwt.ui.shared.SimulatorResultsDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.SimulatorUISelectionDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindFieldDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindFieldDTO.WindData;
|
||||
import com.sap.sailing.gwt.ui.shared.WindFieldGenParamsDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindLatticeDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindLatticeGenParamsDTO;
|
||||
@@ -202,13 +204,9 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula
|
||||
Position[][] grid = bd.extractGrid(params.getxRes(), params.getyRes(), params.getBorderY(), params.getBorderX());
|
||||
wf.setPositionGrid(grid);
|
||||
|
||||
TimePoint startTime = new MillisecondsTimePoint(params.getStartTime().getTime());// new
|
||||
// MillisecondsTimePoint(0);
|
||||
TimePoint timeStep = new MillisecondsTimePoint(params.getTimeStep().getTime());// new
|
||||
// MillisecondsTimePoint(30*1000);
|
||||
TimePoint endTime = new MillisecondsTimePoint(params.getEndTime().getTime());// new MillisecondsTimePoint(10 *
|
||||
// 60 * 1000);
|
||||
|
||||
TimePoint startTime = new MillisecondsTimePoint(params.getStartTime().getTime());
|
||||
Duration timeStep = params.getTimeStep();
|
||||
TimePoint endTime = new MillisecondsTimePoint(params.getEndTime().getTime());
|
||||
wf.generate(startTime, null, timeStep);
|
||||
|
||||
if (params.getMode() != SailingSimulatorConstants.ModeMeasured) {
|
||||
@@ -217,8 +215,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula
|
||||
wf.setGridAreaGps(gridAreaGps);
|
||||
}
|
||||
|
||||
WindFieldDTO wfDTO = createWindFieldDTO(wf, startTime, endTime, timeStep, params); //params.isShowStreamlets2(), params.isShowLines(), params.getSeedLines());
|
||||
|
||||
WindFieldDTO wfDTO = createWindFieldDTO(wf, startTime, endTime, timeStep, params);
|
||||
LOGGER.info("Exiting getWindField");
|
||||
return wfDTO;
|
||||
|
||||
@@ -249,7 +246,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula
|
||||
WindFieldGenerator wf = null;
|
||||
List<Position> course = null;
|
||||
TimePoint startTime = new MillisecondsTimePoint(params.getStartTime().getTime());
|
||||
TimePoint timeStep = new MillisecondsTimePoint(params.getTimeStep().getTime());
|
||||
Duration timeStep = params.getTimeStep();
|
||||
|
||||
this.controlParameters.resetBlastRandomStream = params.isKeepState();
|
||||
this.retreiveWindControlParameters(pattern);
|
||||
@@ -724,120 +721,49 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula
|
||||
return result;
|
||||
}
|
||||
|
||||
private WindFieldDTO createWindFieldDTO(WindFieldGenerator wf, TimePoint startTime, TimePoint endTime, TimePoint timeStep, WindFieldGenParamsDTO params) {
|
||||
//boolean isStreamlets, boolean isShowLines, char seedLines) {
|
||||
|
||||
private WindFieldDTO createWindFieldDTO(WindFieldGenerator wf, TimePoint startTime, TimePoint endTime,
|
||||
Duration timeStep, WindFieldGenParamsDTO params) {
|
||||
WindFieldDTO windFieldDTO = new WindFieldDTO();
|
||||
Position[][] positionGrid = wf.getPositionGrid();
|
||||
|
||||
//if (!params.isShowStreamlets2()) {
|
||||
|
||||
List<SimulatorWindDTO> wList = new ArrayList<SimulatorWindDTO>();
|
||||
|
||||
if (positionGrid != null && positionGrid.length > 0) {
|
||||
TimePoint t = startTime;
|
||||
while (t.compareTo(endTime) <= 0) {
|
||||
for (int i = 0; i < positionGrid.length; ++i) {
|
||||
for (int j = 0; j < positionGrid[i].length; ++j) {
|
||||
Wind localWind = wf.getWind(new TimedPositionWithSpeedImpl(t, positionGrid[i][j], null));
|
||||
LOGGER.finer(localWind.toString());
|
||||
wList.add(createSimulatorWindDTO(localWind));
|
||||
}
|
||||
}
|
||||
t = new MillisecondsTimePoint(t.asMillis() + timeStep.asMillis());
|
||||
}
|
||||
TimePoint t = startTime;
|
||||
while (t.compareTo(endTime) <= 0) {
|
||||
for (int i = 0; i < positionGrid.length; ++i) {
|
||||
for (int j = 0; j < positionGrid[i].length; ++j) {
|
||||
Wind localWind = wf.getWind(new TimedPositionWithSpeedImpl(t, positionGrid[i][j], null));
|
||||
LOGGER.finer(localWind.toString());
|
||||
wList.add(createSimulatorWindDTO(localWind));
|
||||
}
|
||||
}
|
||||
t = new MillisecondsTimePoint(t.asMillis() + timeStep.asMillis());
|
||||
}
|
||||
}
|
||||
|
||||
windFieldDTO.setMatrix(wList);
|
||||
|
||||
if (params.isShowLines() && params.getSeedLines() == 'f') {
|
||||
this.getWindLinesFromStartLine(wf, windFieldDTO, startTime, endTime, timeStep);
|
||||
this.getWindLinesFromStartLine(wf, windFieldDTO, startTime, endTime, timeStep);
|
||||
}
|
||||
|
||||
if (params.isShowLines() && params.getSeedLines() == 'b') {
|
||||
this.getWindLinesFromEndLine(wf, windFieldDTO, startTime, endTime, timeStep);
|
||||
this.getWindLinesFromEndLine(wf, windFieldDTO, startTime, endTime, timeStep);
|
||||
}
|
||||
|
||||
windFieldDTO.curBearing = wf.getWindParameters().curBearing;
|
||||
windFieldDTO.curSpeed = wf.getWindParameters().curSpeed;
|
||||
|
||||
//} else {
|
||||
if (params.isShowStreamlets2()) {
|
||||
|
||||
// generate JSON-vectorfield
|
||||
String jsonField = "$wnd.windFieldSim={";
|
||||
//this.rcStart = field.rcStart;
|
||||
jsonField += "rcStart:{lat:" + params.getNorthWest().latDeg + ",lng:" + params.getNorthWest().lngDeg + "},";
|
||||
//this.rcEnd = field.rcEnd;
|
||||
jsonField += "rcEnd:{lat:" + params.getSouthEast().latDeg + ",lng:" + params.getSouthEast().lngDeg + "},";
|
||||
// SouthWest
|
||||
jsonField += "boundsSW:{lat:" + positionGrid[0][0].getLatDeg() + ",lng:" + positionGrid[0][0].getLngDeg() + "},";
|
||||
// NorthEast
|
||||
jsonField += "boundsNE:{lat:" + positionGrid[params.getyRes()+2*params.getBorderY()-1][params.getxRes()+2*params.getBorderX()-1].getLatDeg() + ",lng:" + positionGrid[params.getyRes()+2*params.getBorderY()-1][params.getxRes()+2*params.getBorderX()-1].getLngDeg() + "},";
|
||||
//this.resY = field.resY;
|
||||
jsonField += "resY:" + params.getyRes() + ",";
|
||||
//this.resX = field.resX;
|
||||
jsonField += "resX:" + params.getxRes() + ",";
|
||||
// border
|
||||
jsonField += "borderY:" + params.getBorderY() + ",";
|
||||
jsonField += "borderX:" + params.getBorderX() + ",";
|
||||
//this.xScale = field.xScale;
|
||||
jsonField += "xScale:1.5,";
|
||||
|
||||
/*String jsonField2 = "data:[";
|
||||
DecimalFormat df = new DecimalFormat("#.00");
|
||||
double maxWindSpeed = 0;
|
||||
|
||||
//List<SimulatorWindDTO> windMat = wfDTO.getMatrix();
|
||||
|
||||
if (positionGrid != null && positionGrid.length > 0) {
|
||||
TimePoint t = startTime;
|
||||
while (t.compareTo(endTime) <= 0) {
|
||||
jsonField2 += "[";
|
||||
for (int i = 0; i < positionGrid.length; ++i) {
|
||||
jsonField2 += "[";
|
||||
for (int j = 0; j < positionGrid[i].length; ++j) {
|
||||
Wind localWind = wf.getWind(new TimedPositionWithSpeedImpl(t, positionGrid[i][j], null));
|
||||
if (localWind.getKnots() > maxWindSpeed) {
|
||||
maxWindSpeed = localWind.getKnots();
|
||||
}
|
||||
double windY = localWind.getKnots() * Math.cos(localWind.getBearing().getRadians());
|
||||
double windX = localWind.getKnots() * Math.sin(localWind.getBearing().getRadians());
|
||||
jsonField2 += df.format(windX) + "," + df.format(windY);
|
||||
if (j<(params.getxRes()-1)) {
|
||||
jsonField2 += ",";
|
||||
}
|
||||
}
|
||||
if (i<(params.getyRes()-1)) {
|
||||
jsonField2 += "],";
|
||||
} else {
|
||||
jsonField2 += "]";
|
||||
}
|
||||
}
|
||||
t = new MillisecondsTimePoint(t.asMillis() + timeStep.asMillis());
|
||||
if (t.compareTo(endTime) <= 0) {
|
||||
jsonField2 += "],";
|
||||
} else {
|
||||
jsonField2 += "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
jsonField += "maxLength:" + maxWindSpeed + "," + jsonField2;
|
||||
jsonField += "]";
|
||||
|
||||
// finalize JSON Field
|
||||
jsonField += "};";*/
|
||||
|
||||
//LOGGER.info("JSON Field:\n" + jsonField);
|
||||
windFieldDTO.windDataJSON = jsonField;
|
||||
// windFieldDTO.setMatrix(null);
|
||||
WindData windData = new WindData();
|
||||
windData.rcStart = params.getNorthWest();
|
||||
windData.rcEnd = params.getSouthEast();
|
||||
windData.resX = params.getxRes();
|
||||
windData.resY = params.getyRes();
|
||||
windData.borderX = params.getBorderX();
|
||||
windData.borderY = params.getBorderY();
|
||||
windData.xScale = 1.5;
|
||||
windFieldDTO.windData = windData;
|
||||
}
|
||||
|
||||
return windFieldDTO;
|
||||
}
|
||||
|
||||
private void getWindLinesFromStartLine(WindFieldGenerator wf, WindFieldDTO windFieldDTO, TimePoint startTime, TimePoint endTime, TimePoint timeStep) {
|
||||
|
||||
private void getWindLinesFromStartLine(WindFieldGenerator wf, WindFieldDTO windFieldDTO, TimePoint startTime,
|
||||
TimePoint endTime, Duration timeStep) {
|
||||
Position[][] positionGrid = wf.getPositionGrid();
|
||||
WindLinesDTO windLinesDTO = windFieldDTO.getWindLinesDTO();
|
||||
if (windLinesDTO == null) {
|
||||
@@ -875,8 +801,8 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula
|
||||
//logger.info("Added : " + windFieldDTO.getWindLinesDTO().getWindLinesMap().size() + " wind lines");
|
||||
}
|
||||
|
||||
private void getWindLinesFromEndLine(WindFieldGenerator wf, WindFieldDTO windFieldDTO, TimePoint startTime, TimePoint endTime, TimePoint timeStep) {
|
||||
|
||||
private void getWindLinesFromEndLine(WindFieldGenerator wf, WindFieldDTO windFieldDTO, TimePoint startTime,
|
||||
TimePoint endTime, Duration timeStep) {
|
||||
Position[][] positionGrid = wf.getPositionGrid();
|
||||
WindLinesDTO windLinesDTO = windFieldDTO.getWindLinesDTO();
|
||||
if (windLinesDTO == null) {
|
||||
|
||||
+2
-1
@@ -49,7 +49,8 @@ public class RaceInfoDTO implements IsSerializable {
|
||||
public Date lastUpdateTime;
|
||||
public Flags lastUpperFlag;
|
||||
public Flags lastLowerFlag;
|
||||
public boolean isLastFlagDisplayed;
|
||||
public boolean lastFlagsAreDisplayed;
|
||||
public boolean lastFlagsDisplayedStateChanged;
|
||||
public boolean isRaceAbortedInPassBefore;
|
||||
public boolean isTracked;
|
||||
public RaceCourseDTO lastCourseDesign;
|
||||
|
||||
+12
-1
@@ -3,13 +3,24 @@ package com.sap.sailing.gwt.ui.shared;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import com.sap.sailing.domain.common.dto.PositionDTO;
|
||||
|
||||
public class WindFieldDTO implements IsSerializable {
|
||||
|
||||
public double curSpeed;
|
||||
public double curBearing;
|
||||
|
||||
public String windDataJSON;
|
||||
public static class WindData implements IsSerializable {
|
||||
public PositionDTO rcStart;
|
||||
public PositionDTO rcEnd;
|
||||
public int resX;
|
||||
public int resY;
|
||||
public int borderX;
|
||||
public int borderY;
|
||||
public double xScale;
|
||||
}
|
||||
|
||||
public WindData windData;
|
||||
|
||||
private List<SimulatorWindDTO> matrix;
|
||||
|
||||
|
||||
+5
-4
@@ -3,6 +3,7 @@ package com.sap.sailing.gwt.ui.shared;
|
||||
import java.util.Date;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import com.sap.sailing.domain.common.Duration;
|
||||
import com.sap.sailing.domain.common.dto.PositionDTO;
|
||||
|
||||
public class WindFieldGenParamsDTO implements IsSerializable {
|
||||
@@ -17,7 +18,7 @@ public class WindFieldGenParamsDTO implements IsSerializable {
|
||||
|
||||
private Date startTime;
|
||||
private Date endTime;
|
||||
private Date timeStep;
|
||||
private Duration timeStep;
|
||||
|
||||
private boolean keepState;
|
||||
|
||||
@@ -111,15 +112,15 @@ public class WindFieldGenParamsDTO implements IsSerializable {
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
public Date getTimeStep() {
|
||||
public Duration getTimeStep() {
|
||||
return timeStep;
|
||||
}
|
||||
|
||||
public void setTimeStep(Date timeStep) {
|
||||
public void setTimeStep(Duration timeStep) {
|
||||
this.timeStep = timeStep;
|
||||
}
|
||||
|
||||
public void setDefaultTimeSettings(Date startTime, Date timeStep, Date endTime) {
|
||||
public void setDefaultTimeSettings(Date startTime, Duration timeStep, Date endTime) {
|
||||
this.startTime = startTime;
|
||||
this.timeStep = timeStep;
|
||||
this.endTime = endTime;
|
||||
|
||||
+1
-1
@@ -173,7 +173,7 @@ public class PathCanvasOverlay extends WindFieldCanvasOverlay implements Named {
|
||||
}
|
||||
index++;
|
||||
|
||||
long timeStep = windParams.getTimeStep().getTime();
|
||||
long timeStep = windParams.getTimeStep().asMillis();
|
||||
if ((windDTO.timepoint - startTime) % (timeStep) == 0) {
|
||||
drawPoint(windDTO);
|
||||
}
|
||||
|
||||
+44
-4
@@ -27,7 +27,9 @@ public class SimulatorEntryPoint extends AbstractEntryPoint {
|
||||
private int xRes = 40;
|
||||
private int yRes = 20;
|
||||
private int border = 0;
|
||||
private int particles = 0;
|
||||
|
||||
private StreamletParameters streamletPars = new StreamletParameters();
|
||||
|
||||
private boolean autoUpdate = false;
|
||||
private char mode = SailingSimulatorConstants.ModeEvent; // default mode: 'e'vent
|
||||
private char event = SailingSimulatorConstants.EventKielerWoche; // default event: 'k'ieler woche
|
||||
@@ -41,6 +43,7 @@ public class SimulatorEntryPoint extends AbstractEntryPoint {
|
||||
private boolean showStreamlets2 = false; // show animated wind streamlets in the wind display and replay modes.
|
||||
private boolean injectWindDataJS = false;
|
||||
|
||||
|
||||
private static Logger logger = Logger.getLogger(SimulatorEntryPoint.class.getName());
|
||||
|
||||
@Override
|
||||
@@ -79,12 +82,49 @@ public class SimulatorEntryPoint extends AbstractEntryPoint {
|
||||
} else {
|
||||
this.border = Integer.parseInt(border);
|
||||
}
|
||||
|
||||
streamletPars.particles = 0;
|
||||
streamletPars.motionScale = 1.0;
|
||||
streamletPars.swarmScale = 1.0;
|
||||
streamletPars.detailZoom = 15;
|
||||
|
||||
String particlesStr = Window.Location.getParameter("particles");
|
||||
if (particlesStr == null || particlesStr.isEmpty()) {
|
||||
logger.config("Using default particles " + this.particles);
|
||||
logger.config("Using default particles " + this.streamletPars.particles);
|
||||
} else {
|
||||
this.particles = Integer.parseInt(particlesStr);
|
||||
this.streamletPars.particles = Integer.parseInt(particlesStr);
|
||||
}
|
||||
String tmpStr = Window.Location.getParameter("motionScale");
|
||||
if (tmpStr == null || tmpStr.isEmpty()) {
|
||||
logger.config("Using default motionScale.");
|
||||
} else {
|
||||
this.streamletPars.motionScale = Double.parseDouble(tmpStr);
|
||||
}
|
||||
tmpStr = Window.Location.getParameter("swarmScale");
|
||||
if (tmpStr == null || tmpStr.isEmpty()) {
|
||||
logger.config("Using default swarmScale.");
|
||||
} else {
|
||||
this.streamletPars.swarmScale = Double.parseDouble(tmpStr);
|
||||
}
|
||||
tmpStr = Window.Location.getParameter("lineBase");
|
||||
if (tmpStr == null || tmpStr.isEmpty()) {
|
||||
logger.config("Using default lineBase.");
|
||||
} else {
|
||||
this.streamletPars.lineBase = Double.parseDouble(tmpStr);
|
||||
}
|
||||
tmpStr = Window.Location.getParameter("lineScale");
|
||||
if (tmpStr == null || tmpStr.isEmpty()) {
|
||||
logger.config("Using default lineScale.");
|
||||
} else {
|
||||
this.streamletPars.lineScale = Double.parseDouble(tmpStr);
|
||||
}
|
||||
tmpStr = Window.Location.getParameter("detailZoom");
|
||||
if (tmpStr == null || tmpStr.isEmpty()) {
|
||||
logger.config("Using default detailZoom.");
|
||||
} else {
|
||||
this.streamletPars.detailZoom = Integer.parseInt(tmpStr);
|
||||
}
|
||||
|
||||
String autoUpdateStr = Window.Location.getParameter("autoUpdate");
|
||||
if (autoUpdateStr == null || autoUpdateStr.isEmpty()) {
|
||||
logger.config("Using default auto update " + autoUpdate);
|
||||
@@ -179,7 +219,7 @@ public class SimulatorEntryPoint extends AbstractEntryPoint {
|
||||
}
|
||||
|
||||
private void createSimulatorPanel() {
|
||||
SimulatorMainPanel simulatorPanel = new SimulatorMainPanel(simulatorService, stringMessages, this, xRes, yRes, border, particles,
|
||||
SimulatorMainPanel simulatorPanel = new SimulatorMainPanel(simulatorService, stringMessages, this, xRes, yRes, border, streamletPars,
|
||||
autoUpdate, mode, event, showGrid, showLines, seedLines, showArrows, showStreamlets, showStreamlets2, injectWindDataJS, showMapControls);
|
||||
|
||||
DockLayoutPanel p = new DockLayoutPanel(Unit.PX);
|
||||
|
||||
+7
-5
@@ -42,7 +42,9 @@ import com.google.gwt.user.client.ui.RadioButton;
|
||||
import com.google.gwt.user.client.ui.RequiresResize;
|
||||
import com.google.gwt.user.client.ui.SimplePanel;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.sap.sailing.domain.common.Duration;
|
||||
import com.sap.sailing.domain.common.dto.BoatClassDTO;
|
||||
import com.sap.sailing.domain.common.impl.MillisecondsDurationImpl;
|
||||
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
||||
import com.sap.sailing.gwt.ui.client.SimulatorServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
@@ -227,11 +229,11 @@ public class SimulatorMainPanel extends SimplePanel {
|
||||
return NumberFormat.getFormat("0.0").format(value);
|
||||
}
|
||||
|
||||
public SimulatorMainPanel(SimulatorServiceAsync svc, StringMessages stringMessages, ErrorReporter errorReporter, int xRes, int yRes, int border, int particles, boolean autoUpdate,
|
||||
public SimulatorMainPanel(SimulatorServiceAsync svc, StringMessages stringMessages, ErrorReporter errorReporter, int xRes, int yRes, int border, StreamletParameters streamletPars, boolean autoUpdate,
|
||||
char mode, char event, boolean showGrid, boolean showLines, char seedLines, boolean showArrows, boolean showStreamlets, boolean showStreamlets2, boolean injectWindDataJS, boolean showMapControls) {
|
||||
super();
|
||||
|
||||
this.particles = particles;
|
||||
this.particles = streamletPars.particles;
|
||||
|
||||
this.simulatorSvc = svc;
|
||||
this.stringMessages = stringMessages;
|
||||
@@ -306,13 +308,13 @@ public class SimulatorMainPanel extends SimplePanel {
|
||||
TimeRangeWithZoomProvider timeRangeProvider = new TimeRangeWithZoomModel();
|
||||
initTimer();
|
||||
timer.setTime(windParams.getStartTime().getTime());
|
||||
int secondsTimeStep = (int) windParams.getTimeStep().getTime() / 1000;
|
||||
int secondsTimeStep = (int) windParams.getTimeStep().asSeconds();
|
||||
timer.setPlaySpeedFactor(secondsTimeStep);
|
||||
timePanel = new TimePanel<TimePanelSettings>(timer, timeRangeProvider, stringMessages, false);
|
||||
|
||||
busyIndicator = new SimpleBusyIndicator(false, 0.8f);
|
||||
|
||||
simulatorMap = new SimulatorMap(simulatorSvc, stringMessages, errorReporter, xRes, yRes, border, timer, timePanel,
|
||||
simulatorMap = new SimulatorMap(simulatorSvc, stringMessages, errorReporter, xRes, yRes, border, streamletPars, timer, timePanel,
|
||||
windParams, busyIndicator, mode, this, showMapControls);
|
||||
simulatorMap.setSize("100%", "100%");
|
||||
|
||||
@@ -363,7 +365,7 @@ public class SimulatorMainPanel extends SimplePanel {
|
||||
long epochTime = (defaultNow.getTime()/86400000)*86400000 - utcOffSet*3600000; // today, midnight in default time zone
|
||||
|
||||
Date startTime = new Date(epochTime);
|
||||
Date timeStep = new Date(15 * 1000);
|
||||
Duration timeStep = new MillisecondsDurationImpl(15 * 1000);
|
||||
Date endTime = new Date(startTime.getTime() + 10 * 60 * 1000);
|
||||
windParams.setDefaultTimeSettings(startTime, timeStep, endTime);
|
||||
}
|
||||
|
||||
+37
-4
@@ -101,6 +101,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ
|
||||
private int xRes;
|
||||
private int yRes;
|
||||
private int border;
|
||||
private StreamletParameters streamletPars;
|
||||
private boolean warningAlreadyShown = false;
|
||||
private SimulatorMainPanel parent = null;
|
||||
private PathPolyline pathPolyline = null;
|
||||
@@ -296,7 +297,36 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ
|
||||
|
||||
}
|
||||
|
||||
public SimulatorMap(SimulatorServiceAsync simulatorSvc, StringMessages stringMessages, ErrorReporter errorReporter, int xRes, int yRes, int border, Timer timer,
|
||||
/*public SimulatorMap(SimulatorServiceAsync simulatorSvc, StringMessages stringMessages, ErrorReporter errorReporter, int xRes, int yRes, int border, StreamletParameters streamletPars, Timer timer,
|
||||
WindFieldGenParamsDTO windParams, SimpleBusyIndicator busyIndicator, char mode,
|
||||
SimulatorMainPanel parent) {
|
||||
this.simulatorService = simulatorSvc;
|
||||
this.stringMessages = stringMessages;
|
||||
this.errorReporter = errorReporter;
|
||||
this.xRes = xRes;
|
||||
this.yRes = yRes;
|
||||
this.border = border;
|
||||
this.timer = timer;
|
||||
this.timePanel = null;
|
||||
timer.addTimeListener(this);
|
||||
this.windParams = windParams;
|
||||
this.busyIndicator = busyIndicator;
|
||||
this.mode = mode;
|
||||
this.colorPalette = new ColorPaletteGenerator();
|
||||
this.dataInitialized = false;
|
||||
this.overlaysInitialized = false;
|
||||
this.windFieldCanvasOverlay = null;
|
||||
this.windLineGuidesCanvasOverlay = null;
|
||||
this.windGridCanvasOverlay = null;
|
||||
this.windLineCanvasOverlay = null;
|
||||
this.replayPathCanvasOverlays = null;
|
||||
this.raceCourseCanvasOverlay = null;
|
||||
this.timeListeners = new LinkedList<TimeListenerWithStoppingCriteria>();
|
||||
this.initializeData();
|
||||
this.parent = parent;
|
||||
}*/
|
||||
|
||||
public SimulatorMap(SimulatorServiceAsync simulatorSvc, StringMessages stringMessages, ErrorReporter errorReporter, int xRes, int yRes, int border, StreamletParameters streamletPars, Timer timer,
|
||||
TimePanel<TimePanelSettings> timePanel, WindFieldGenParamsDTO windParams, SimpleBusyIndicator busyIndicator, char mode, SimulatorMainPanel parent, boolean showMapControls) {
|
||||
this.simulatorService = simulatorSvc;
|
||||
this.stringMessages = stringMessages;
|
||||
@@ -304,6 +334,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ
|
||||
this.xRes = xRes;
|
||||
this.yRes = yRes;
|
||||
this.border = border;
|
||||
this.streamletPars = streamletPars;
|
||||
this.timer = timer;
|
||||
this.timePanel = timePanel;
|
||||
timer.addTimeListener(this);
|
||||
@@ -549,7 +580,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ
|
||||
windLineGuidesCanvasOverlay = new WindLineGuidesCanvasOverlay(map, SimulatorMapOverlaysZIndexes.WINDSTREAMLETS_ZINDEX, timer, xRes);
|
||||
}
|
||||
if (windParams.isShowStreamlets2()) {
|
||||
windStreamletsCanvasOverlay = new WindStreamletsCanvasOverlay(this, SimulatorMapOverlaysZIndexes.WINDFIELD_ZINDEX, timer, windParams);
|
||||
windStreamletsCanvasOverlay = new WindStreamletsCanvasOverlay(this, SimulatorMapOverlaysZIndexes.WINDFIELD_ZINDEX, timer, streamletPars, windParams);
|
||||
}
|
||||
if (windParams.isShowGrid()) {
|
||||
windGridCanvasOverlay = new WindGridCanvasOverlay(map, SimulatorMapOverlaysZIndexes.WINDGRID_ZINDEX, timer, xRes, yRes);
|
||||
@@ -612,6 +643,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ
|
||||
windParams.setyRes(yRes);
|
||||
windParams.setBorder(border);
|
||||
busyIndicator.setBusy(true);
|
||||
timer.pause();
|
||||
timer.setTime(windParams.getStartTime().getTime());
|
||||
|
||||
simulatorService.getWindField(windParams, windPatternDisplay, new AsyncCallback<WindFieldDTO>() {
|
||||
@@ -734,6 +766,7 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ
|
||||
windParams.setBorder(border);
|
||||
|
||||
busyIndicator.setBusy(true);
|
||||
timer.pause();
|
||||
timer.setTime(windParams.getStartTime().getTime());
|
||||
|
||||
simulatorService.getSimulatorResults(mode, raceCourseDirection, windParams, windPatternDisplay, true, selection, new ResultManager(summaryView));
|
||||
@@ -1090,8 +1123,8 @@ public class SimulatorMap extends AbsolutePanel implements RequiresDataInitializ
|
||||
|
||||
map.panTo(regattaAreaCanvasOverlay.getCurrentCourseArea().getCenterPos());
|
||||
mapPan = true;
|
||||
if (map.getZoom() < 14) {
|
||||
map.setZoom(14);
|
||||
if (map.getZoom() < streamletPars.detailZoom) {
|
||||
map.setZoom(streamletPars.detailZoom);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.sap.sailing.gwt.ui.simulator;
|
||||
|
||||
public class StreamletParameters {
|
||||
|
||||
public int particles = 0;
|
||||
public double motionScale = 1.0;
|
||||
public double swarmScale = 1.0;
|
||||
public int detailZoom = 15;
|
||||
public double lineScale = 1.0;
|
||||
public double lineBase = 1.5;
|
||||
|
||||
public StreamletParameters() {
|
||||
}
|
||||
|
||||
}
|
||||
+12
-166
@@ -1,16 +1,7 @@
|
||||
package com.sap.sailing.gwt.ui.simulator;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.json.client.JSONArray;
|
||||
import com.google.gwt.json.client.JSONNumber;
|
||||
import com.google.gwt.json.client.JSONObject;
|
||||
import com.google.gwt.json.client.JSONString;
|
||||
import com.google.gwt.maps.client.base.LatLng;
|
||||
import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindFieldDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindFieldGenParamsDTO;
|
||||
import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay;
|
||||
@@ -18,42 +9,36 @@ import com.sap.sailing.gwt.ui.simulator.streamlets.Swarm;
|
||||
import com.sap.sse.gwt.client.player.Timer;
|
||||
|
||||
/**
|
||||
* A google map overlay based on a HTML5 canvas for drawing a wind field. The overlay covers the whole map and displays
|
||||
* A Google Maps overlay based on an HTML5 canvas for drawing a wind field. The overlay covers the whole map and displays
|
||||
* the wind objects inside it.
|
||||
*
|
||||
* @author Nidhi Sawhney (D054070)
|
||||
*
|
||||
*/
|
||||
public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements TimeListenerWithStoppingCriteria {
|
||||
|
||||
/** The wind field that is to be displayed in the overlay */
|
||||
protected WindFieldDTO windFieldDTO;
|
||||
protected WindFieldGenParamsDTO windParams = null;
|
||||
protected SimulatorMap simulatorMap;
|
||||
private WindFieldDTO windFieldDTO;
|
||||
private final WindFieldGenParamsDTO windParams;
|
||||
private final SimulatorMap simulatorMap;
|
||||
private StreamletParameters streamletPars;
|
||||
|
||||
private boolean visible = false;
|
||||
private Timer timer;
|
||||
private final Timer timer;
|
||||
private Date endDate;
|
||||
|
||||
private int nParticles;
|
||||
private Swarm swarm;
|
||||
|
||||
private static Logger logger = Logger.getLogger(WindStreamletsCanvasOverlay.class.getName());
|
||||
|
||||
public WindStreamletsCanvasOverlay(SimulatorMap simulatorMap, int zIndex, final Timer timer,
|
||||
public WindStreamletsCanvasOverlay(SimulatorMap simulatorMap, int zIndex, final Timer timer, final StreamletParameters streamletPars,
|
||||
final WindFieldGenParamsDTO windParams) {
|
||||
super(simulatorMap.getMap(), zIndex);
|
||||
|
||||
this.simulatorMap = simulatorMap;
|
||||
this.timer = timer;
|
||||
this.windParams = windParams;
|
||||
|
||||
this.streamletPars = streamletPars;
|
||||
this.nParticles = this.simulatorMap.getMainPanel().particles;
|
||||
|
||||
windFieldDTO = null;
|
||||
|
||||
getCanvas().getElement().setId("swarm-display");
|
||||
|
||||
}
|
||||
|
||||
public WindFieldDTO getWindFieldDTO() {
|
||||
@@ -64,96 +49,10 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti
|
||||
return this.windParams;
|
||||
}
|
||||
|
||||
public JavaScriptObject getJSONRandomWindData() {
|
||||
|
||||
JSONObject jsonWindData = new JSONObject();
|
||||
|
||||
jsonWindData.put("timestamp", new JSONString("11:55 am on March 18, 2014"));
|
||||
|
||||
LatLng boundsSW = LatLng.newInstance(53.854617, 8.159124);
|
||||
LatLng boundsNE = LatLng.newInstance(55.263922, 11.413824);
|
||||
|
||||
jsonWindData.put("x0", new JSONNumber(boundsSW.getLongitude()));
|
||||
jsonWindData.put("y0", new JSONNumber(boundsSW.getLatitude()));
|
||||
jsonWindData.put("x1", new JSONNumber(boundsNE.getLongitude()));
|
||||
jsonWindData.put("y1", new JSONNumber(boundsNE.getLatitude()));
|
||||
|
||||
int maxSteps = 1;
|
||||
int gridWidth = 100;
|
||||
int gridHeight = 100;
|
||||
jsonWindData.put("gridWidth", new JSONNumber(gridWidth));
|
||||
jsonWindData.put("gridHeight", new JSONNumber(gridHeight));
|
||||
|
||||
JSONArray windField = new JSONArray();
|
||||
|
||||
for (int stp = 0; stp < maxSteps; stp++) {
|
||||
JSONArray vectorField = new JSONArray();
|
||||
for (int idx = 0; idx < (gridWidth * gridHeight * 2); idx++) {
|
||||
vectorField.set(idx, new JSONNumber((Math.random() - 0.5) * 10.0 + 3.0));
|
||||
// windField.set(idx, new JSONNumber(10.0));
|
||||
}
|
||||
windField.set(stp, vectorField);
|
||||
}
|
||||
jsonWindData.put("field", windField);
|
||||
|
||||
return jsonWindData.getJavaScriptObject();
|
||||
}
|
||||
|
||||
public native boolean isSwarmDataExt() /*-{
|
||||
if ($wnd.swarmDataExt) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}-*/;
|
||||
|
||||
public native String getSwarmData() /*-{
|
||||
return JSON.stringify($wnd.swarmData);
|
||||
}-*/;
|
||||
|
||||
public native void setSwarmData(JavaScriptObject swarmData) /*-{
|
||||
$wnd.swarmData = swarmData;
|
||||
}-*/;
|
||||
|
||||
private native void getJSNIWind(WindStreamletsCanvasOverlay wsc) /*-{
|
||||
$wnd.getWindfromSimulator = function(idx) {
|
||||
return wsc.@com.sap.sailing.gwt.ui.simulator.WindStreamletsCanvasOverlay::getWind(I)(idx);
|
||||
};
|
||||
}-*/;
|
||||
|
||||
private native JavaScriptObject getWindInst(double x, double y) /*-{
|
||||
return {
|
||||
x : x,
|
||||
y : y
|
||||
};
|
||||
}-*/;
|
||||
|
||||
public JavaScriptObject getWind(int idx) {
|
||||
SimulatorWindDTO wind = this.windFieldDTO.getMatrix().get(idx);
|
||||
double y = wind.trueWindSpeedInKnots * Math.cos(wind.trueWindBearingDeg * Math.PI / 180.0);
|
||||
double x = wind.trueWindSpeedInKnots * Math.sin(wind.trueWindBearingDeg * Math.PI / 180.0);
|
||||
return getWindInst(x, y);
|
||||
}
|
||||
|
||||
public native void setWindDataJSON(String jsonField) /*-{
|
||||
eval(jsonField);
|
||||
}-*/;
|
||||
|
||||
public WindStreamletsCanvasOverlay(SimulatorMap simulatorMap, int zIndex) {
|
||||
this(simulatorMap, zIndex, null, null);
|
||||
}
|
||||
|
||||
public native void setMapInstance(Object mapInstance) /*-{
|
||||
$wnd.swarmMap = mapInstance;
|
||||
}-*/;
|
||||
|
||||
public native void setCanvasProjectionInstance(Object instance) /*-{
|
||||
$wnd.swarmCanvasProjection = instance;
|
||||
}-*/;
|
||||
|
||||
public void startStreamlets() {
|
||||
if (swarm == null) {
|
||||
this.swarm = new Swarm(this, map);
|
||||
this.swarm = new Swarm(this, map, this.streamletPars);
|
||||
}
|
||||
this.swarm.start(40, windFieldDTO);
|
||||
}
|
||||
@@ -168,46 +67,6 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti
|
||||
}
|
||||
}
|
||||
|
||||
public void extendWindDataJSON() {
|
||||
List<SimulatorWindDTO> data = this.windFieldDTO.getMatrix();
|
||||
String jsonData = "data:[";
|
||||
int p = 0;
|
||||
int imax = this.windParams.getyRes() + 2 * this.windParams.getBorderY();
|
||||
int jmax = this.windParams.getxRes() + 2 * this.windParams.getBorderX();
|
||||
int steps = data.size() / (imax * jmax);
|
||||
double maxWindSpeed = 0;
|
||||
for (int s = 0; s < steps; s++) {
|
||||
jsonData += "[";
|
||||
for (int i = 0; i < imax; i++) {
|
||||
jsonData += "[";
|
||||
for (int j = 0; j < jmax; j++) {
|
||||
SimulatorWindDTO wind = data.get(p);
|
||||
p++;
|
||||
if (wind.trueWindSpeedInKnots > maxWindSpeed) {
|
||||
maxWindSpeed = wind.trueWindSpeedInKnots;
|
||||
}
|
||||
double y = wind.trueWindSpeedInKnots * Math.cos(wind.trueWindBearingDeg * Math.PI / 180.0);
|
||||
double x = wind.trueWindSpeedInKnots * Math.sin(wind.trueWindBearingDeg * Math.PI / 180.0);
|
||||
jsonData += x + "," + y;
|
||||
if (j < (jmax - 1)) {
|
||||
jsonData += ",";
|
||||
}
|
||||
}
|
||||
if (i < (imax - 1)) {
|
||||
jsonData += "],";
|
||||
} else {
|
||||
jsonData += "]";
|
||||
}
|
||||
}
|
||||
if (s < (steps - 1)) {
|
||||
jsonData += "],";
|
||||
} else {
|
||||
jsonData += "]";
|
||||
}
|
||||
}
|
||||
this.windFieldDTO.windDataJSON += jsonData + "],maxLength:" + maxWindSpeed + "};";
|
||||
}
|
||||
|
||||
public void setEndDate(Date endDate) {
|
||||
this.endDate = endDate;
|
||||
}
|
||||
@@ -261,8 +120,8 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti
|
||||
super.draw();
|
||||
if (mapProjection != null) {
|
||||
if ((nParticles > 0) && (swarm == null)) {
|
||||
this.swarm = new Swarm(this, map);
|
||||
this.swarm.start(40, null);
|
||||
this.swarm = new Swarm(this, map, this.streamletPars);
|
||||
this.swarm.start(/* animationIntervalMillis */ 40, /* windField */ null);
|
||||
}
|
||||
if (windFieldDTO != null) {
|
||||
// drawing is done by external JavaScript for Streamlets
|
||||
@@ -274,14 +133,9 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti
|
||||
return this.swarm;
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
this.stopStreamlets();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void timeChanged(final Date newDate, Date oldDate) {
|
||||
int step = (int) ((newDate.getTime() - this.windParams.getStartTime().getTime()) / this.windParams
|
||||
.getTimeStep().getTime());
|
||||
int step = (int) ((newDate.getTime() - this.windParams.getStartTime().getTime()) / this.windParams.getTimeStep().asMillis());
|
||||
this.setStreamletsStep(step);
|
||||
}
|
||||
|
||||
@@ -293,12 +147,4 @@ public class WindStreamletsCanvasOverlay extends FullCanvasOverlay implements Ti
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTimer(final Timer timer) {
|
||||
this.timer = timer;
|
||||
}
|
||||
|
||||
public Timer getTimer() {
|
||||
return timer;
|
||||
}
|
||||
}
|
||||
+9
-35
@@ -37,23 +37,12 @@ public abstract class FullCanvasOverlay extends CanvasOverlayV3 implements Requi
|
||||
|
||||
public FullCanvasOverlay(MapWidget map, int zIndex) {
|
||||
super(map, zIndex);
|
||||
|
||||
/*getMap().addDragEndHandler( new DragEndMapHandler() {
|
||||
@Override
|
||||
public void onEvent(DragEndMapEvent event) {
|
||||
// TODO Auto-generated method stub
|
||||
draw();
|
||||
};
|
||||
});*/
|
||||
|
||||
getMap().addCenterChangeHandler(new CenterChangeMapHandler() {
|
||||
@Override
|
||||
public void onEvent(CenterChangeMapEvent event) {
|
||||
// TODO Auto-generated method stub
|
||||
drawCenterChanged();
|
||||
};
|
||||
@Override
|
||||
public void onEvent(CenterChangeMapEvent event) {
|
||||
drawCenterChanged();
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,11 +146,11 @@ public abstract class FullCanvasOverlay extends CanvasOverlayV3 implements Requi
|
||||
drawCircle(x, y,radius,color);
|
||||
context2d.setGlobalAlpha(1.0f);
|
||||
if (getMap().getZoom() >= 11) {
|
||||
context2d.setFillStyle(textColor);
|
||||
double fontsize = 9.0 + (12.0-9.0)*(getMap().getZoom() - 10.0)/(12.0-10.0);
|
||||
context2d.setFont("normal "+fontsize+"px Calibri");
|
||||
context2d.fillText(text, x + 0.7*fontsize, y + 0.3*fontsize);
|
||||
//System.out.println("ZoomLevel: "+getMap().getZoomLevel()+", Fontsize: "+fontsize);
|
||||
context2d.setFillStyle(textColor);
|
||||
double fontsize = 9.0 + (12.0 - 9.0) * (getMap().getZoom() - 10.0) / (12.0 - 10.0);
|
||||
context2d.setFont("normal " + fontsize + "px Calibri");
|
||||
context2d.fillText(text, x + 0.7 * fontsize, y + 0.3 * fontsize);
|
||||
// System.out.println("ZoomLevel: "+getMap().getZoomLevel()+", Fontsize: "+fontsize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,40 +209,27 @@ public abstract class FullCanvasOverlay extends CanvasOverlayV3 implements Requi
|
||||
String msg = "Wind @ P" + index + ": time : " + windDTO.timepoint + " speed: " + windDTO.trueWindSpeedInKnots
|
||||
+ "knots " + windDTO.trueWindBearingDeg;
|
||||
logger.fine(msg);
|
||||
|
||||
PositionDTO position = windDTO.position;
|
||||
|
||||
LatLng positionLatLng = LatLng.newInstance(position.latDeg, position.lngDeg);
|
||||
Point canvasPositionInPx = mapProjection.fromLatLngToDivPixel(positionLatLng);
|
||||
|
||||
double x = canvasPositionInPx.getX() - getWidgetPosLeft();
|
||||
double y = canvasPositionInPx.getY() - getWidgetPosTop();
|
||||
|
||||
//windFieldPoints.put(new ToolTip(x, y), windDTO);
|
||||
|
||||
drawArrowPx(x, y, angle, length, weight, drawHead, color);
|
||||
}
|
||||
|
||||
protected void drawArrowPx(double x, double y, double angle, double length, double weight, boolean drawHead, String color) {
|
||||
final double dx = length * Math.sin(angle);
|
||||
final double dy = -length * Math.cos(angle);
|
||||
|
||||
final double x1 = x + dx / 2;
|
||||
final double y1 = y + dy / 2;
|
||||
|
||||
drawLine(x - dx / 2, y - dy / 2, x1, y1, weight, color);
|
||||
|
||||
final double theta = Math.atan2(-dy, dx);
|
||||
|
||||
final double hLength = Math.max(6.,6.+(10./(60.-10.))*Math.max(length-6.,0));
|
||||
logger.finer("headlength: "+hLength+", arrowlength: "+length);
|
||||
|
||||
if (drawHead) {
|
||||
drawHead(x1, y1, theta, hLength, weight, color);
|
||||
}
|
||||
//String text = "P" + index;// + NumberFormat.getFormat("0.00").format(windDTO.trueWindBearingDeg) + "�";
|
||||
//drawPointWithText(x, y, text);
|
||||
//drawPoint(x, y);
|
||||
}
|
||||
|
||||
protected void drawHead(final double x, final double y, final double theta, final double headLength, final double weight, String color) {
|
||||
@@ -265,7 +241,6 @@ public abstract class FullCanvasOverlay extends CanvasOverlayV3 implements Requi
|
||||
if (t2 <= (-Math.PI)) {
|
||||
t2 += 2 * Math.PI;
|
||||
}
|
||||
|
||||
final double x1 = (x - Math.cos(t) * headLength);
|
||||
final double y1 = (y + Math.sin(t) * headLength);
|
||||
final double x1o = (x + Math.cos(t) * weight/2);
|
||||
@@ -274,7 +249,6 @@ public abstract class FullCanvasOverlay extends CanvasOverlayV3 implements Requi
|
||||
final double y2 = (y + Math.sin(t2) * headLength);
|
||||
final double x2o = (x + Math.cos(t2) * weight/2);
|
||||
final double y2o = (y - Math.sin(t2) * weight/2);
|
||||
|
||||
drawLine(x1o, y1o, x1, y1, weight, color);
|
||||
drawLine(x2o, y2o, x2, y2, weight, color);
|
||||
}
|
||||
|
||||
+6
-7
@@ -1,12 +1,11 @@
|
||||
package com.sap.sailing.gwt.ui.simulator.streamlets;
|
||||
|
||||
public class Index {
|
||||
public final long x;
|
||||
public final long y;
|
||||
|
||||
public long x;
|
||||
public long y;
|
||||
|
||||
public Index(long x, long y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
public Index(long x, long y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
+14
-15
@@ -1,20 +1,19 @@
|
||||
package com.sap.sailing.gwt.ui.simulator.streamlets;
|
||||
|
||||
public class Neighbors {
|
||||
public final int xBot;
|
||||
public final int xTop;
|
||||
public final double xMod;
|
||||
public final int yBot;
|
||||
public final int yTop;
|
||||
public final double yMod;
|
||||
|
||||
public int xBot;
|
||||
public int xTop;
|
||||
public double xMod;
|
||||
public int yBot;
|
||||
public int yTop;
|
||||
public double yMod;
|
||||
|
||||
public Neighbors(int xTop, int yTop, int xBot, int yBot, double xMod, double yMod) {
|
||||
this.xTop = xTop;
|
||||
this.yTop = yTop;
|
||||
this.xBot = xBot;
|
||||
this.yBot = yBot;
|
||||
this.xMod = xMod;
|
||||
this.yMod = yMod;
|
||||
}
|
||||
public Neighbors(int xTop, int yTop, int xBot, int yBot, double xMod, double yMod) {
|
||||
this.xTop = xTop;
|
||||
this.yTop = yTop;
|
||||
this.xBot = xBot;
|
||||
this.yBot = yBot;
|
||||
this.xMod = xMod;
|
||||
this.yMod = yMod;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -3,9 +3,9 @@ package com.sap.sailing.gwt.ui.simulator.streamlets;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
|
||||
public class Particle {
|
||||
public Position pos;
|
||||
public Vector pxOld;
|
||||
public double speed;
|
||||
public int age;
|
||||
public Position currentPosition;
|
||||
public Vector previousPixelCoordinate;
|
||||
public Vector currentPixelCoordinate;
|
||||
public int stepsToLive;
|
||||
public Vector v;
|
||||
}
|
||||
+15
-39
@@ -3,8 +3,11 @@ package com.sap.sailing.gwt.ui.simulator.streamlets;
|
||||
import com.google.gwt.json.client.JSONObject;
|
||||
import com.google.gwt.json.client.JSONParser;
|
||||
import com.google.gwt.maps.client.base.LatLng;
|
||||
import com.sap.sailing.domain.common.Bounds;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.impl.BoundsImpl;
|
||||
import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
import com.sap.sailing.gwt.ui.simulator.StreamletParameters;
|
||||
|
||||
public class RectField implements VectorField {
|
||||
|
||||
@@ -15,8 +18,7 @@ public class RectField implements VectorField {
|
||||
private final double y0;
|
||||
private final double y1;
|
||||
|
||||
private Position visSW;
|
||||
private Position visNE;
|
||||
private double motionFactor;
|
||||
|
||||
private final int w;
|
||||
private final int h;
|
||||
@@ -25,16 +27,13 @@ public class RectField implements VectorField {
|
||||
private final double particleFactor;
|
||||
private final String[] colorsForSpeeds;
|
||||
|
||||
public RectField(Vector[][] field, double x0, double y0, double x1, double y1) {
|
||||
public RectField(Vector[][] field, double x0, double y0, double x1, double y1, StreamletParameters parameters) {
|
||||
colorsForSpeeds = createColorsForSpeeds();
|
||||
this.x0 = x0;
|
||||
this.x1 = x1;
|
||||
this.y0 = y0;
|
||||
this.y1 = y1;
|
||||
|
||||
this.visSW = new DegreePosition(0.0, 0.0);
|
||||
this.visNE = new DegreePosition(0.0, 0.0);
|
||||
|
||||
this.motionFactor = 0.9 * parameters.motionScale;
|
||||
this.field = field;
|
||||
this.w = field.length;
|
||||
this.h = field[0].length;
|
||||
@@ -57,7 +56,7 @@ public class RectField implements VectorField {
|
||||
my = (my / this.h) * (y1 - y0) + y0;
|
||||
}
|
||||
|
||||
public static RectField read(String jsonData, boolean correctForSphere) {
|
||||
public static RectField read(String jsonData, boolean correctForSphere, StreamletParameters parameters) {
|
||||
JSONObject data = JSONParser.parseLenient(jsonData).isObject();
|
||||
int w = (int) data.get("gridWidth").isNumber().doubleValue();
|
||||
int h = (int) data.get("gridHeight").isNumber().doubleValue();
|
||||
@@ -82,7 +81,7 @@ public class RectField implements VectorField {
|
||||
}
|
||||
}
|
||||
RectField result = new RectField(field, data.get("x0").isNumber().doubleValue(), data.get("y0").isNumber()
|
||||
.doubleValue(), data.get("x1").isNumber().doubleValue(), data.get("y1").isNumber().doubleValue());
|
||||
.doubleValue(), data.get("x1").isNumber().doubleValue(), data.get("y1").isNumber().doubleValue(), parameters);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -98,15 +97,6 @@ public class RectField implements VectorField {
|
||||
public void prevStep() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getRandomPosition() {
|
||||
double rndY = Math.random();
|
||||
double rndX = Math.random();
|
||||
double latDeg = rndY * this.visSW.getLatDeg() + (1 - rndY) * this.visNE.getLatDeg();
|
||||
double lngDeg = rndX * this.visSW.getLngDeg() + (1 - rndX) * this.visNE.getLngDeg();
|
||||
return new DegreePosition(latDeg, lngDeg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inBounds(Position p) {
|
||||
return p.getLngDeg() >= this.x0 && p.getLngDeg() < this.x1 && p.getLatDeg() >= this.y0
|
||||
@@ -151,12 +141,12 @@ public class RectField implements VectorField {
|
||||
|
||||
@Override
|
||||
public double motionScale(int zoomLevel) {
|
||||
return 0.9 * Math.pow(1.7, Math.min(1.0, 6.0 - zoomLevel));
|
||||
return this.motionFactor * Math.pow(1.7, Math.min(1.0, 6.0 - zoomLevel));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double particleWeight(Position p, Vector v) {
|
||||
return 1.0 - v.length() / this.maxLength;
|
||||
return v == null ? 0 : (1.0 - v.length() / this.maxLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -185,25 +175,11 @@ public class RectField implements VectorField {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position[] getFieldCorners() {
|
||||
DegreePosition[] result = new DegreePosition[2];
|
||||
result[0] = new DegreePosition(Math.min(this.y0, this.y1), Math.min(this.x0, this.x1));
|
||||
result[1] = new DegreePosition(Math.max(this.y0, this.y1), Math.max(this.x0, this.x1));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisNE(Position visNE) {
|
||||
this.visNE = visNE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisSW(Position visSW) {
|
||||
this.visSW = visSW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisFullCanvas(boolean full) {
|
||||
public Bounds getFieldCorners() {
|
||||
// FIXME this is not date line-safe. If the field crosses +180°E (the international date line), this will fail
|
||||
Position sw = new DegreePosition(Math.min(this.y0, this.y1), Math.min(this.x0, this.x1));
|
||||
Position ne = new DegreePosition(Math.max(this.y0, this.y1), Math.max(this.x0, this.x1));
|
||||
return new BoundsImpl(sw, ne);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+72
-131
@@ -3,13 +3,14 @@ package com.sap.sailing.gwt.ui.simulator.streamlets;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.json.client.JSONObject;
|
||||
import com.google.gwt.json.client.JSONParser;
|
||||
import com.sap.sailing.domain.common.Bounds;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.impl.BoundsImpl;
|
||||
import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindFieldDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindFieldGenParamsDTO;
|
||||
import com.sap.sailing.gwt.ui.simulator.StreamletParameters;
|
||||
|
||||
public class SimulatorField implements VectorField {
|
||||
private boolean swarmDebug = false;
|
||||
@@ -17,60 +18,56 @@ public class SimulatorField implements VectorField {
|
||||
private Position rcStart;
|
||||
private Position rcEnd;
|
||||
|
||||
private int resY;
|
||||
private int resX;
|
||||
private final int resY;
|
||||
private final int resX;
|
||||
|
||||
private int borderY;
|
||||
private int borderX;
|
||||
private final int borderY;
|
||||
private final int borderX;
|
||||
|
||||
private double bdXi;
|
||||
private double bdPhi;
|
||||
private final Position bdA;
|
||||
private final Position bdB;
|
||||
private final Position bdC;
|
||||
|
||||
private Position bdA;
|
||||
private Position bdB;
|
||||
private Position bdC;
|
||||
private final double xScale;
|
||||
private double motionFactor;
|
||||
private double lineBase;
|
||||
private double lineScale;
|
||||
private final double maxLength;
|
||||
private final double particleFactor;
|
||||
|
||||
private double xScale;
|
||||
/**
|
||||
* The cosine of the field's average latitude (arithmetic average of NW's latitude and SE's latitude) which represents
|
||||
* the average ratio of pixels used per longitude and latitude angle in the Mercator projection. At the equator, this
|
||||
* ration is 1; towards the poles it gets less.
|
||||
*/
|
||||
private final double lngScale;
|
||||
|
||||
private Position visSW;
|
||||
private Position visNE;
|
||||
private boolean visFull = false;
|
||||
private final Position nvY;
|
||||
private final Position nvX;
|
||||
|
||||
private double maxLength;
|
||||
private double particleFactor;
|
||||
|
||||
private double lngScale;
|
||||
|
||||
private Position nvY;
|
||||
private Position nvX;
|
||||
private Position gvX;
|
||||
|
||||
private double[][][] data;
|
||||
private final double[][][] data;
|
||||
private final String[] colorsForSpeeds;
|
||||
private int step;
|
||||
|
||||
public SimulatorField(WindFieldDTO windData, WindFieldGenParamsDTO windParams) {
|
||||
public SimulatorField(WindFieldDTO windData, WindFieldGenParamsDTO windParams, StreamletParameters parameters) {
|
||||
this.colorsForSpeeds = createColorsForSpeeds();
|
||||
this.step = 0;
|
||||
String parseString = windData.windDataJSON.substring(18, windData.windDataJSON.length() - 1) + "}";
|
||||
JSONObject baseData = JSONParser.parseLenient(parseString).isObject();
|
||||
this.rcStart = new DegreePosition(baseData.get("rcStart").isObject().get("lat").isNumber().doubleValue(),
|
||||
baseData.get("rcStart").isObject().get("lng").isNumber().doubleValue());
|
||||
this.rcEnd = new DegreePosition(baseData.get("rcEnd").isObject().get("lat").isNumber().doubleValue(), baseData
|
||||
.get("rcEnd").isObject().get("lng").isNumber().doubleValue());
|
||||
this.resY = (int) baseData.get("resY").isNumber().doubleValue();
|
||||
this.resX = (int) baseData.get("resX").isNumber().doubleValue();
|
||||
this.borderY = (int) baseData.get("borderY").isNumber().doubleValue();
|
||||
this.borderX = (int) baseData.get("borderX").isNumber().doubleValue();
|
||||
this.bdXi = (this.borderY + 0.5) / (this.resY - 1);
|
||||
this.bdPhi = 1.0 + 2 * this.bdXi;
|
||||
this.rcStart = new DegreePosition(windData.windData.rcStart.latDeg, windData.windData.rcStart.lngDeg);
|
||||
this.rcEnd = new DegreePosition(windData.windData.rcEnd.latDeg, windData.windData.rcEnd.lngDeg);
|
||||
this.resX = windData.windData.resX;
|
||||
this.resY = windData.windData.resY;
|
||||
this.borderX = windData.windData.borderX;
|
||||
this.borderY = windData.windData.borderY;
|
||||
final double bdXi = (this.borderY + 0.5) / (this.resY - 1);
|
||||
final double bdPhi = 1.0 + 2 * bdXi;
|
||||
this.bdA = new DegreePosition(this.rcEnd.getLatDeg() + (this.rcEnd.getLatDeg() - this.rcStart.getLatDeg())
|
||||
* this.bdXi, this.rcEnd.getLngDeg() + (this.rcEnd.getLngDeg() - this.rcStart.getLngDeg()) * this.bdXi);
|
||||
this.bdB = new DegreePosition((this.rcStart.getLatDeg() - this.rcEnd.getLatDeg()) * this.bdPhi,
|
||||
(this.rcStart.getLngDeg() - this.rcEnd.getLngDeg()) * this.bdPhi);
|
||||
this.xScale = baseData.get("xScale").isNumber().doubleValue();
|
||||
this.visSW = new DegreePosition(0.0, 0.0);
|
||||
this.visNE = new DegreePosition(0.0, 0.0);
|
||||
* bdXi, this.rcEnd.getLngDeg() + (this.rcEnd.getLngDeg() - this.rcStart.getLngDeg()) * bdXi);
|
||||
this.bdB = new DegreePosition((this.rcStart.getLatDeg() - this.rcEnd.getLatDeg()) * bdPhi,
|
||||
(this.rcStart.getLngDeg() - this.rcEnd.getLngDeg()) * bdPhi);
|
||||
this.xScale = windData.windData.xScale;
|
||||
this.motionFactor = 0.07 * parameters.motionScale;
|
||||
this.lineBase = parameters.lineBase;
|
||||
this.lineScale = parameters.lineScale;
|
||||
List<SimulatorWindDTO> gridData = windData.getMatrix();
|
||||
int p = 0;
|
||||
int imax = windParams.getyRes() + 2 * windParams.getBorderY();
|
||||
@@ -78,7 +75,8 @@ public class SimulatorField implements VectorField {
|
||||
int steps = gridData.size() / (imax * jmax);
|
||||
this.data = new double[steps][imax][2 * jmax];
|
||||
double maxWindSpeed = 0;
|
||||
double minWindSpeed = 100;
|
||||
double minWindSpeed = Double.MAX_VALUE;
|
||||
// copy the wind data from WindFieldDTO.getMatrix() to the data array, determining min/max wind speed
|
||||
for (int s = 0; s < steps; s++) {
|
||||
for (int i = 0; i < imax; i++) {
|
||||
for (int j = 0; j < jmax; j++) {
|
||||
@@ -99,37 +97,20 @@ public class SimulatorField implements VectorField {
|
||||
}
|
||||
this.maxLength = maxWindSpeed;
|
||||
this.particleFactor = 2.0;
|
||||
double latAvg = (this.rcEnd.getLatDeg() + this.rcStart.getLatDeg()) / 2.;
|
||||
final double latAvg = (this.rcEnd.getLatDeg() + this.rcStart.getLatDeg()) / 2.;
|
||||
this.lngScale = Math.cos(latAvg * Math.PI / 180.0);
|
||||
double difLat = this.rcEnd.getLatDeg() - this.rcStart.getLatDeg();
|
||||
double difLng = (this.rcEnd.getLngDeg() - this.rcStart.getLngDeg()) * this.lngScale;
|
||||
double difLen = Math.sqrt(difLat * difLat + difLng * difLng);
|
||||
final double difLat = this.rcEnd.getLatDeg() - this.rcStart.getLatDeg();
|
||||
final double difLng = (this.rcEnd.getLngDeg() - this.rcStart.getLngDeg()) * this.lngScale;
|
||||
final double difLen = Math.sqrt(difLat * difLat + difLng * difLng);
|
||||
this.nvY = new DegreePosition(difLat / difLen / difLen * (this.resY - 1), difLng / difLen / difLen
|
||||
* (this.resY - 1));
|
||||
double nrmLat = -difLng / difLen;
|
||||
double nrmLng = difLat / difLen;
|
||||
this.nvX = new DegreePosition(nrmLat / this.xScale / difLen * (this.resX - 1), nrmLng / this.xScale / difLen
|
||||
* (this.resX - 1));
|
||||
this.gvX = new DegreePosition(nrmLat * this.xScale * difLen, nrmLng / this.lngScale * this.xScale * difLen);
|
||||
this.bdC = new DegreePosition(this.gvX.getLatDeg() * (this.resX + 2 * this.borderX - 1) / (this.resX - 1),
|
||||
this.gvX.getLngDeg() * (this.resX + 2 * this.borderX - 1) / (this.resX - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getRandomPosition() {
|
||||
final Position result;
|
||||
if (this.visFull) {
|
||||
double rndY = Math.random();
|
||||
double rndX = Math.random();
|
||||
double latDeg = rndY * this.visSW.getLatDeg() + (1 - rndY) * this.visNE.getLatDeg();
|
||||
double lngDeg = rndX * this.visSW.getLngDeg() + (1 - rndX) * this.visNE.getLngDeg();
|
||||
result = new DegreePosition(latDeg, lngDeg);
|
||||
} else {
|
||||
double rndY = Math.random();
|
||||
double rndX = Math.random() - 0.5;
|
||||
result = this.getInnerPosition(rndX, rndY);
|
||||
}
|
||||
return result;
|
||||
final Position gvX = new DegreePosition(nrmLat * this.xScale * difLen, nrmLng / this.lngScale * this.xScale * difLen);
|
||||
this.bdC = new DegreePosition(gvX.getLatDeg() * (this.resX + 2 * this.borderX - 1) / (this.resX - 1),
|
||||
gvX.getLngDeg() * (this.resX + 2 * this.borderX - 1) / (this.resX - 1));
|
||||
}
|
||||
|
||||
private Position getInnerPosition(double factX, double factY) {
|
||||
@@ -151,19 +132,20 @@ public class SimulatorField implements VectorField {
|
||||
}
|
||||
|
||||
private Vector interpolate(Position p) {
|
||||
Neighbors idx = this.getNeighbors(p);
|
||||
Neighbors idx = getNeighbors(p);
|
||||
if (swarmDebug
|
||||
&& ((idx.xTop >= (this.resX + 2 * this.borderX)) || (idx.yTop >= (this.resY + 2 * this.borderY)))) {
|
||||
GWT.log("interpolate: out of range: " + idx.xTop + " " + idx.yTop);
|
||||
}
|
||||
double avgX = this.data[this.step][idx.yBot][2 * idx.xBot] * (1 - idx.yMod) * (1 - idx.xMod)
|
||||
+ this.data[this.step][idx.yTop][2 * idx.xBot] * idx.yMod * (1 - idx.xMod)
|
||||
+ this.data[this.step][idx.yBot][2 * idx.xTop] * (1 - idx.yMod) * idx.xMod
|
||||
+ this.data[this.step][idx.yTop][2 * idx.xTop] * idx.yMod * idx.xMod;
|
||||
double avgY = this.data[this.step][idx.yBot][2 * idx.xBot + 1] * (1 - idx.yMod) * (1 - idx.xMod)
|
||||
+ this.data[this.step][idx.yTop][2 * idx.xBot + 1] * idx.yMod * (1 - idx.xMod)
|
||||
+ this.data[this.step][idx.yBot][2 * idx.xTop + 1] * (1 - idx.yMod) * idx.xMod
|
||||
+ this.data[this.step][idx.yTop][2 * idx.xTop + 1] * idx.yMod * idx.xMod;
|
||||
final double[][] dataAtStep = this.data[this.step];
|
||||
double avgX = dataAtStep[idx.yBot][2 * idx.xBot] * (1 - idx.yMod) * (1 - idx.xMod)
|
||||
+ dataAtStep[idx.yTop][2 * idx.xBot] * idx.yMod * (1 - idx.xMod)
|
||||
+ dataAtStep[idx.yBot][2 * idx.xTop] * (1 - idx.yMod) * idx.xMod
|
||||
+ dataAtStep[idx.yTop][2 * idx.xTop] * idx.yMod * idx.xMod;
|
||||
double avgY = dataAtStep[idx.yBot][2 * idx.xBot + 1] * (1 - idx.yMod) * (1 - idx.xMod)
|
||||
+ dataAtStep[idx.yTop][2 * idx.xBot + 1] * idx.yMod * (1 - idx.xMod)
|
||||
+ dataAtStep[idx.yBot][2 * idx.xTop + 1] * (1 - idx.yMod) * idx.xMod
|
||||
+ dataAtStep[idx.yTop][2 * idx.xTop + 1] * idx.yMod * idx.xMod;
|
||||
return new Vector(avgX / this.lngScale, avgY);
|
||||
}
|
||||
|
||||
@@ -199,30 +181,11 @@ public class SimulatorField implements VectorField {
|
||||
|
||||
private Index getIndex(Position p) {
|
||||
// calculate grid indexes
|
||||
Position posR = new DegreePosition(p.getLatDeg() - this.rcStart.getLatDeg(),
|
||||
(p.getLngDeg() - this.rcStart.getLngDeg()) * this.lngScale);
|
||||
|
||||
final double latOffset = p.getLatDeg() - this.rcStart.getLatDeg();
|
||||
final double lngOffset = (p.getLngDeg() - this.rcStart.getLngDeg()) * this.lngScale;
|
||||
// closest grid point
|
||||
long yIdx = Math.round(posR.getLatDeg() * this.nvY.getLatDeg() + posR.getLngDeg() * this.nvY.getLngDeg())
|
||||
+ this.borderY;
|
||||
long xIdx = Math.round(posR.getLatDeg() * this.nvX.getLatDeg() + posR.getLngDeg() * this.nvX.getLngDeg()
|
||||
+ (this.resX - 1) / 2.)
|
||||
+ this.borderX;
|
||||
|
||||
if (this.visFull) {
|
||||
if (yIdx >= (this.resY + 2 * this.borderY)) {
|
||||
yIdx = this.resY + 2 * this.borderY - 1;
|
||||
}
|
||||
if (yIdx < 0) {
|
||||
yIdx = 0;
|
||||
}
|
||||
if (xIdx >= (this.resX + 2 * this.borderX)) {
|
||||
xIdx = this.resX + 2 * this.borderX - 1;
|
||||
}
|
||||
if (xIdx < 0) {
|
||||
xIdx = 0;
|
||||
}
|
||||
}
|
||||
long yIdx = Math.round(latOffset * this.nvY.getLatDeg() + lngOffset * this.nvY.getLngDeg()) + this.borderY;
|
||||
long xIdx = Math.round(latOffset * this.nvX.getLatDeg() + lngOffset * this.nvX.getLngDeg() + (this.resX - 1) / 2.) + this.borderX;
|
||||
return new Index(xIdx, yIdx);
|
||||
}
|
||||
|
||||
@@ -271,12 +234,12 @@ public class SimulatorField implements VectorField {
|
||||
|
||||
@Override
|
||||
public double motionScale(int zoomLevel) {
|
||||
return 0.07 * Math.pow(1.6, Math.min(1.0, 6.0 - zoomLevel));
|
||||
return this.motionFactor * Math.pow(1.6, Math.min(1.0, 6.0 - zoomLevel));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double particleWeight(Position p, Vector v) {
|
||||
return v.length() / this.maxLength + 0.1;
|
||||
return v == null ? 0 : (v.length() / this.maxLength + 0.1);
|
||||
}
|
||||
|
||||
private String[] createColorsForSpeeds() {
|
||||
@@ -317,49 +280,27 @@ public class SimulatorField implements VectorField {
|
||||
* absolute linewidth speed == 12kn => linewidth 1.5 speed == 24kn => linewidth 3.0 speed == 6kn => linewidth
|
||||
* 0.75
|
||||
*/
|
||||
return Math.round(speed / 8.0 * 100.0) / 100.0;
|
||||
return Math.max(0.01, Math.round((lineBase + lineScale * (speed - 12.0) / 8.0) * 100.0) / 100.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position[] getFieldCorners() {
|
||||
public Bounds getFieldCorners() {
|
||||
// FIXME this is not date line-safe. If the field crosses +180°E (the international date line), this will fail
|
||||
Position fieldNE = this.getInnerPosition(+0.5, 1.0);
|
||||
Position fieldSW = this.getInnerPosition(-0.5, 0.0);
|
||||
Position fieldSE = this.getInnerPosition(+0.5, 0.0);
|
||||
Position fieldNW = this.getInnerPosition(-0.5, 1.0);
|
||||
|
||||
DegreePosition[] result = new DegreePosition[2];
|
||||
|
||||
double minLat = Math.min(Math.min(fieldNE.getLatDeg(), fieldSW.getLatDeg()),
|
||||
Math.min(fieldNW.getLatDeg(), fieldSE.getLatDeg()));
|
||||
double minLng = Math.min(Math.min(fieldNE.getLngDeg(), fieldSW.getLngDeg()),
|
||||
Math.min(fieldNW.getLngDeg(), fieldSE.getLngDeg()));
|
||||
result[0] = new DegreePosition(minLat, minLng);
|
||||
|
||||
Position sw = new DegreePosition(minLat, minLng);
|
||||
double maxLat = Math.max(Math.max(fieldNE.getLatDeg(), fieldSW.getLatDeg()),
|
||||
Math.max(fieldNW.getLatDeg(), fieldSE.getLatDeg()));
|
||||
double maxLng = Math.max(Math.max(fieldNE.getLngDeg(), fieldSW.getLngDeg()),
|
||||
Math.max(fieldNW.getLngDeg(), fieldSE.getLngDeg()));
|
||||
result[1] = new DegreePosition(maxLat, maxLng);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisNE(Position visNE) {
|
||||
this.visNE = visNE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisSW(Position visSW) {
|
||||
this.visSW = visSW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisFullCanvas(boolean full) {
|
||||
if (this.visFull != full) {
|
||||
System.out.println("visFull: " + full);
|
||||
this.visFull = full;
|
||||
}
|
||||
Position ne = new DegreePosition(maxLat, maxLng);
|
||||
return new BoundsImpl(sw, ne);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+87
-91
@@ -7,9 +7,12 @@ import com.google.gwt.canvas.dom.client.Context2d;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.maps.client.MapWidget;
|
||||
import com.google.gwt.user.client.Timer;
|
||||
import com.sap.sailing.domain.common.Bounds;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.impl.BoundsImpl;
|
||||
import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
import com.sap.sailing.gwt.ui.shared.WindFieldDTO;
|
||||
import com.sap.sailing.gwt.ui.simulator.StreamletParameters;
|
||||
import com.sap.sailing.gwt.ui.simulator.WindStreamletsCanvasOverlay;
|
||||
import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay;
|
||||
|
||||
@@ -17,37 +20,55 @@ public class Swarm {
|
||||
private final FullCanvasOverlay fullcanvas;
|
||||
private final Canvas canvas;
|
||||
private final MapWidget map;
|
||||
private StreamletParameters parameters;
|
||||
|
||||
private Timer loopTimer;
|
||||
private Mercator projection;
|
||||
private VectorField field;
|
||||
|
||||
/**
|
||||
* The number of particles to show. After {@link #updateBounds()} has been run, this also reflects the size of the
|
||||
* {@link #particles} array.
|
||||
*/
|
||||
private int nParticles;
|
||||
|
||||
/**
|
||||
* The particles shown in this swarm.
|
||||
*/
|
||||
private Particle[] particles;
|
||||
|
||||
/**
|
||||
* Tells if nothing of this swarm is currently visible on the {@link #map}. This is the case when there is no
|
||||
* intersection between the {@link #field vector field's} {@link VectorField#getFieldCorners() bounds} and the
|
||||
* visible map area.
|
||||
*/
|
||||
private boolean swarmOffScreen = false;
|
||||
|
||||
private int swarmPause = 0;
|
||||
private boolean swarmContinue = true;
|
||||
private Position boundsNE;
|
||||
private Position boundsSW;
|
||||
private Bounds visibleBoundsOfField;
|
||||
|
||||
public Swarm(FullCanvasOverlay fullcanvas, MapWidget map) {
|
||||
public Swarm(FullCanvasOverlay fullcanvas, MapWidget map, StreamletParameters streamletPars) {
|
||||
this.fullcanvas = fullcanvas;
|
||||
this.canvas = fullcanvas.getCanvas();
|
||||
this.map = map;
|
||||
this.parameters = streamletPars;
|
||||
}
|
||||
|
||||
public void start(int animationDuration, WindFieldDTO windField) {
|
||||
public void start(int animationIntervalMillis, WindFieldDTO windField) {
|
||||
projection = new Mercator(fullcanvas, map);
|
||||
// TODO make the VectorField a parameter of Swarm
|
||||
if (windField == null) {
|
||||
SimulatorJSBundle bundle = GWT.create(SimulatorJSBundle.class);
|
||||
String jsonStr = bundle.windStreamletsDataJS().getText();
|
||||
RectField f = RectField.read(jsonStr.substring(19, jsonStr.length() - 1), false);
|
||||
RectField f = RectField.read(jsonStr.substring(19, jsonStr.length() - 1), false, this.parameters);
|
||||
field = f;
|
||||
map.setZoom(5);
|
||||
map.panTo(f.getCenter());
|
||||
projection.calibrate();
|
||||
} else {
|
||||
field = new SimulatorField(((WindStreamletsCanvasOverlay) fullcanvas).getWindFieldDTO(),
|
||||
((WindStreamletsCanvasOverlay) fullcanvas).getWindParams());
|
||||
((WindStreamletsCanvasOverlay) fullcanvas).getWindParams(), this.parameters);
|
||||
fullcanvas.setCanvasSettings();
|
||||
projection.calibrate();
|
||||
}
|
||||
@@ -56,36 +77,47 @@ public class Swarm {
|
||||
ctxt.setFillStyle("red");
|
||||
particles = this.createParticles();
|
||||
this.swarmContinue = true;
|
||||
startLoop(animationDuration);
|
||||
startLoop(animationIntervalMillis);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.swarmContinue = false;
|
||||
}
|
||||
|
||||
public Particle createParticle() {
|
||||
private Particle createParticle() {
|
||||
Particle particle = new Particle();
|
||||
boolean done = false;
|
||||
// try to create a particle at a random position until the weight is high enough for it to be displayed
|
||||
while (!done) {
|
||||
particle.pos = field.getRandomPosition();
|
||||
Vector v = field.getVector(particle.pos);
|
||||
double weight = field.particleWeight(particle.pos, v);
|
||||
particle.currentPosition = getRandomPosition();
|
||||
Vector v = field.getVector(particle.currentPosition);
|
||||
double weight = field.particleWeight(particle.currentPosition, v);
|
||||
if (weight >= Math.random()) {
|
||||
if (v.length() == 0) {
|
||||
particle.age = 0;
|
||||
if (v == null || v.length() == 0) {
|
||||
particle.stepsToLive = 0;
|
||||
} else {
|
||||
particle.age = 1 + (int) Math.round(Math.random() * 40);
|
||||
particle.stepsToLive = 1 + (int) Math.round(Math.random() * 40);
|
||||
}
|
||||
particle.pxOld = projection.latlng2pixel(particle.pos);
|
||||
particle.speed = 0;
|
||||
particle.currentPixelCoordinate = projection.latlng2pixel(particle.currentPosition);
|
||||
particle.previousPixelCoordinate = particle.currentPixelCoordinate;
|
||||
particle.v = v;
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
return particle;
|
||||
}
|
||||
|
||||
private Position getRandomPosition() {
|
||||
final Position result;
|
||||
double rndY = Math.random();
|
||||
double rndX = Math.random();
|
||||
double latDeg = rndY * this.visibleBoundsOfField.getSouthWest().getLatDeg() + (1 - rndY) * this.visibleBoundsOfField.getNorthEast().getLatDeg();
|
||||
double lngDeg = rndX * this.visibleBoundsOfField.getSouthWest().getLngDeg() + (1 - rndX) * this.visibleBoundsOfField.getNorthEast().getLngDeg();
|
||||
result = new DegreePosition(latDeg, lngDeg);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Particle[] createParticles() {
|
||||
private Particle[] createParticles() {
|
||||
Particle[] newParticles = new Particle[nParticles];
|
||||
for (int idx = 0; idx < newParticles.length; idx++) {
|
||||
newParticles[idx] = this.createParticle();
|
||||
@@ -98,64 +130,21 @@ public class Swarm {
|
||||
swarmPause = 5;
|
||||
}
|
||||
|
||||
public void updateBounds() {
|
||||
Position mapNE = new DegreePosition(map.getBounds().getNorthEast().getLatitude(), map.getBounds()
|
||||
.getNorthEast().getLongitude());
|
||||
Position mapSW = new DegreePosition(map.getBounds().getSouthWest().getLatitude(), map.getBounds()
|
||||
.getSouthWest().getLongitude());
|
||||
Position[] fieldCorners = this.field.getFieldCorners();
|
||||
Position fieldNE = fieldCorners[1];
|
||||
Position fieldSW = fieldCorners[0];
|
||||
Vector visibleNE = this.isVisible(fieldNE);
|
||||
Vector visibleSW = this.isVisible(fieldSW);
|
||||
boolean useBoundsNorth = (visibleNE.y == 0);
|
||||
boolean useBoundsEast = (visibleNE.x == 0);
|
||||
boolean useBoundsSouth = (visibleSW.y == 0);
|
||||
boolean useBoundsWest = (visibleSW.x == 0);
|
||||
swarmOffScreen = (visibleNE.y > 0) || (visibleSW.y < 0) || (visibleNE.x < 0) || (visibleSW.x > 0);
|
||||
if (swarmOffScreen) {
|
||||
this.boundsNE = fieldNE;
|
||||
this.boundsSW = fieldSW;
|
||||
} else {
|
||||
if ((!useBoundsNorth) && (!useBoundsEast)) {
|
||||
this.boundsNE = mapNE;
|
||||
} else if (!useBoundsNorth) {
|
||||
this.boundsNE = new DegreePosition(mapNE.getLatDeg(), fieldNE.getLngDeg());
|
||||
} else if (!useBoundsEast) {
|
||||
this.boundsNE = new DegreePosition(fieldNE.getLatDeg(), mapNE.getLngDeg());
|
||||
} else {
|
||||
this.boundsNE = fieldNE;
|
||||
}
|
||||
if ((!useBoundsSouth) && (!useBoundsWest)) {
|
||||
this.boundsSW = mapSW;
|
||||
} else if (!useBoundsSouth) {
|
||||
this.boundsSW = new DegreePosition(mapSW.getLatDeg(), fieldSW.getLngDeg());
|
||||
} else if (!useBoundsWest) {
|
||||
this.boundsSW = new DegreePosition(fieldSW.getLatDeg(), mapSW.getLngDeg());
|
||||
} else {
|
||||
this.boundsSW = fieldSW;
|
||||
}
|
||||
}
|
||||
this.field.setVisSW(this.boundsSW);
|
||||
this.field.setVisNE(this.boundsNE);
|
||||
this.field.setVisFullCanvas((!useBoundsNorth) && (!useBoundsEast) && (!useBoundsSouth) && (!useBoundsWest));
|
||||
Vector boundsSWpx = this.projection.latlng2pixel(this.boundsSW);
|
||||
Vector boundsNEpx = this.projection.latlng2pixel(this.boundsNE);
|
||||
private void updateBounds() {
|
||||
Bounds fieldBounds = this.field.getFieldCorners();
|
||||
final BoundsImpl mapBounds = new BoundsImpl(
|
||||
new DegreePosition(map.getBounds().getSouthWest().getLatitude(), map.getBounds().getSouthWest().getLongitude()),
|
||||
new DegreePosition(map.getBounds().getNorthEast().getLatitude(), map.getBounds().getNorthEast().getLongitude()));
|
||||
swarmOffScreen = !fieldBounds.intersects(mapBounds);
|
||||
visibleBoundsOfField = fieldBounds.intersect(mapBounds);
|
||||
Vector boundsSWpx = this.projection.latlng2pixel(visibleBoundsOfField.getSouthWest());
|
||||
Vector boundsNEpx = this.projection.latlng2pixel(visibleBoundsOfField.getNorthEast());
|
||||
double boundsWidthpx = Math.abs(boundsNEpx.x - boundsSWpx.x);
|
||||
double boundsHeightpx = Math.abs(boundsSWpx.y - boundsNEpx.y);
|
||||
this.nParticles = (int) Math.round(Math.sqrt(boundsWidthpx * boundsHeightpx) * this.field.getParticleFactor());
|
||||
this.nParticles = (int) Math.round(Math.sqrt(boundsWidthpx * boundsHeightpx) * this.field.getParticleFactor() * this.parameters.swarmScale);
|
||||
};
|
||||
|
||||
public Vector isVisible(Position pos) {
|
||||
// test for visibility of swarm
|
||||
Vector proj = this.projection.latlng2pixel(pos);
|
||||
Vector result = new Vector();
|
||||
result.x = (proj.x < 0 ? -1 : 0) + (proj.x > canvas.getOffsetWidth() ? 1 : 0);
|
||||
result.y = (proj.y < 0 ? -1 : 0) + (proj.y > canvas.getOffsetHeight() ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void startLoop(final int millis) {
|
||||
private void startLoop(final int animationIntervalMillis) {
|
||||
// Create animation-loop based on timer timeout
|
||||
loopTimer = new com.google.gwt.user.client.Timer() {
|
||||
public void run() {
|
||||
@@ -169,23 +158,21 @@ public class Swarm {
|
||||
particles = createParticles();
|
||||
swarmPause = 0;
|
||||
}
|
||||
// console("swarmOffScreen:"+swarmOffScreen);
|
||||
if ((!swarmOffScreen) && (swarmPause == 0)) {
|
||||
execute();
|
||||
}
|
||||
Date time1 = new Date();
|
||||
// console("delta:"+(time1.getTime()-time0.getTime())+"/"+millis);
|
||||
if (swarmContinue) {
|
||||
loopTimer.schedule((int) Math.max(10, millis - (time1.getTime() - time0.getTime())));
|
||||
loopTimer.schedule((int) Math.max(10, animationIntervalMillis - (time1.getTime() - time0.getTime())));
|
||||
} else {
|
||||
projection.clearCanvas();
|
||||
}
|
||||
}
|
||||
};
|
||||
loopTimer.schedule(millis);
|
||||
loopTimer.schedule(animationIntervalMillis);
|
||||
}
|
||||
|
||||
public void drawSwarm() {
|
||||
private void drawSwarm() {
|
||||
Context2d ctxt = canvas.getContext2d();
|
||||
ctxt.setGlobalAlpha(0.08);
|
||||
ctxt.setGlobalCompositeOperation("destination-out");
|
||||
@@ -196,35 +183,44 @@ public class Swarm {
|
||||
ctxt.setFillStyle("white");
|
||||
for (int idx = 0; idx < particles.length; idx++) {
|
||||
Particle particle = particles[idx];
|
||||
if (particle.age == 0) {
|
||||
if (particle.stepsToLive == 0) {
|
||||
continue;
|
||||
}
|
||||
ctxt.setLineWidth(field.lineWidth(particle.speed));
|
||||
ctxt.setStrokeStyle(field.getColor(particle.speed));
|
||||
double particleSpeed = particle.v == null ? 0 : particle.v.length();
|
||||
ctxt.setLineWidth(field.lineWidth(particleSpeed));
|
||||
ctxt.setStrokeStyle(field.getColor(particleSpeed));
|
||||
ctxt.beginPath();
|
||||
ctxt.moveTo(particle.pxOld.x, particle.pxOld.y);
|
||||
particle.pxOld = projection.latlng2pixel(particle.pos);
|
||||
ctxt.lineTo(particle.pxOld.x, particle.pxOld.y);
|
||||
ctxt.moveTo(particle.previousPixelCoordinate.x, particle.previousPixelCoordinate.y);
|
||||
ctxt.lineTo(particle.currentPixelCoordinate.x, particle.currentPixelCoordinate.y);
|
||||
ctxt.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean execute() {
|
||||
/**
|
||||
* Moves each particle by its vector {@link Particle#v} multiplied by the speed which is 0.01 times the
|
||||
* {@link VectorField#motionScale(int)} at the map's current zoom level.
|
||||
*/
|
||||
private boolean execute() {
|
||||
double speed = 0.01 * field.motionScale(map.getZoom());
|
||||
for (int idx = 0; idx < particles.length; idx++) {
|
||||
Particle particle = particles[idx];
|
||||
if ((particle.age > 0) && (particle.v != null)) {
|
||||
double latDeg = particle.pos.getLatDeg() + speed * particle.v.y;
|
||||
double lngDeg = particle.pos.getLngDeg() + speed * particle.v.x;
|
||||
particle.pos = new DegreePosition(latDeg, lngDeg);
|
||||
particle.speed = particle.v.length();
|
||||
particle.age--;
|
||||
if ((particle.age > 0) && (this.field.inBounds(particle.pos))) {
|
||||
particle.v = field.getVector(particle.pos);
|
||||
if ((particle.stepsToLive > 0) && (particle.v != null)) {
|
||||
// move the particle one step in the direction and with the speed indicated by particle.v and
|
||||
// update its currentPosition, currentPixelCoordinate and previousPixelCoordinate fields;
|
||||
// also, its particle.v field is updated based on its new position from the vector field
|
||||
particle.previousPixelCoordinate = particle.currentPixelCoordinate;
|
||||
double latDeg = particle.currentPosition.getLatDeg() + speed * particle.v.y;
|
||||
double lngDeg = particle.currentPosition.getLngDeg() + speed * particle.v.x;
|
||||
particle.currentPosition = new DegreePosition(latDeg, lngDeg);
|
||||
particle.currentPixelCoordinate = projection.latlng2pixel(particle.currentPosition);
|
||||
particle.stepsToLive--;
|
||||
if ((particle.stepsToLive > 0) && (this.field.inBounds(particle.currentPosition))) {
|
||||
particle.v = field.getVector(particle.currentPosition);
|
||||
} else {
|
||||
particle.v = null;
|
||||
}
|
||||
} else {
|
||||
// particle timed out (age became 0); create a new one
|
||||
particles[idx] = this.createParticle();
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -25,4 +25,9 @@ public class Vector {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{x: "+x+", y: "+y+"}";
|
||||
}
|
||||
}
|
||||
+60
-9
@@ -1,29 +1,72 @@
|
||||
package com.sap.sailing.gwt.ui.simulator.streamlets;
|
||||
|
||||
import com.sap.sailing.domain.common.Bounds;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
|
||||
/**
|
||||
* A field of vectors to display in a streamlet {@link Swarm}. The {@link Vector}s returned by {@link #getVector(Position)}
|
||||
* are used to initialize {@link Particle}s. Their "speed" is the {@link Vector#length() length} of the vector.<p>
|
||||
*
|
||||
* This field provides color strings for each possible speed which are then used to style the particles as they fly through
|
||||
* the swarm.
|
||||
*
|
||||
* @author Christopher Ronnewinkel (D036654)
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public interface VectorField {
|
||||
Position getRandomPosition();
|
||||
|
||||
/**
|
||||
* Tells whether <code>p</code> is within the bounds of this field.
|
||||
*/
|
||||
boolean inBounds(Position p);
|
||||
|
||||
/**
|
||||
* The vector field's value at position <code>p</code>.
|
||||
*
|
||||
* @return the speed/direction vector that tells how a particle will fly at this position in the vector field, or
|
||||
* <code>null</code> if there should not be a flying particle, e.g., because the field does not know how a
|
||||
* particle would fly at this position. An implementation that does not cleverly extrapolate outside the
|
||||
* field's {@link #inBounds(Position) bounds} should return <code>null</code> for out-of-bounds
|
||||
* positions.
|
||||
*/
|
||||
Vector getVector(Position p);
|
||||
|
||||
/**
|
||||
* @return the maximum {@link Vector#length() length} of any vector returned by {@link #getVector(Position)} in this field for
|
||||
* positions that are {@link #inBounds(Position) within bounds}.
|
||||
*/
|
||||
double getMaxLength();
|
||||
|
||||
double motionScale(int zoomLevel);
|
||||
|
||||
/**
|
||||
* A weight between 0.0 and 1.0 (inclusive) that tells the probability at which a particle at position
|
||||
* <code>p</code> and with speed vector <code>v</code> will be shown. 1.0 means it will certainly be shown; 0.0
|
||||
* means it will certainly not be shown.
|
||||
*
|
||||
* @param p
|
||||
* the particle's position
|
||||
* @param v
|
||||
* the particle's speed/direction vector which may be <code>null</code>; in this case, an implementation
|
||||
* should return 0.0 as the particle's weight.
|
||||
*/
|
||||
double particleWeight(Position p, Vector v);
|
||||
|
||||
/**
|
||||
* Computes a line width for a particle flying at a certain speed.
|
||||
*
|
||||
* @param speed
|
||||
* a speed as obtained by computing a {@link Vector}'s {@link Vector#length() length}.
|
||||
*/
|
||||
double lineWidth(double speed);
|
||||
|
||||
Position[] getFieldCorners();
|
||||
|
||||
void setVisNE(Position visNE);
|
||||
|
||||
void setVisSW(Position visSW);
|
||||
|
||||
void setVisFullCanvas(boolean full);
|
||||
/**
|
||||
* @return the north-east and the south-west corner of the rectangular area on a Mercator projection that fully
|
||||
* contains this field's {@link #inBounds(Position) bounds}. Note that the field's
|
||||
* {@link #inBounds(Position) bounds} are not necessarily defined as a rectangle on a Mercator map. There
|
||||
* may be a rotation or some other shape in place.
|
||||
*/
|
||||
Bounds getFieldCorners();
|
||||
|
||||
double getParticleFactor();
|
||||
|
||||
@@ -33,5 +76,13 @@ public interface VectorField {
|
||||
|
||||
void prevStep();
|
||||
|
||||
/**
|
||||
* Computes a color for a particle flying at a certain speed.
|
||||
*
|
||||
* @param speed
|
||||
* a speed as obtained by computing a {@link Vector}'s {@link Vector#length() length}.
|
||||
* @return an RGB color with optional transparency that is a legal color in CSS. Example:
|
||||
* <code>"rgba(1, 2, 3, 0.4)"</code>
|
||||
*/
|
||||
String getColor(double speed);
|
||||
}
|
||||
Loaded 100 of 155 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user