mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-19 20:25:31 +00:00
Merge branch 'master' of ssh://sapsailing.com/home/trac/git into ubilabs--ios--develop--signup
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
<Macro Rewrite $IP $PORT>
|
||||
RewriteRule ^/(.*)$ http://$IP:$PORT/$1 [P,L]
|
||||
ProxyPassReverse / http://$IP:$PORT/
|
||||
ProxyPreserveHost On
|
||||
</Macro>
|
||||
|
||||
<Macro SSL>
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.0.21
|
||||
1.0.22
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ Regatta=Regata
|
||||
CourseArea=Zona de regata
|
||||
BoatClass=Clase de bote
|
||||
Fleet=Flota
|
||||
RaceColumn=Columna de carrera
|
||||
Race=Carrera
|
||||
Competitor=Competidor
|
||||
Boat=Bote
|
||||
|
||||
+5
-4
@@ -1,5 +1,5 @@
|
||||
Speed=Vitesse
|
||||
SpeedTenSecondsBefore=Vitesse 10s avant
|
||||
SpeedTenSecondsBefore=Vitesse 10 s avant
|
||||
DistanceTraveled=Distance parcourue
|
||||
Name=Nom
|
||||
LegType=Type de portion de parcours
|
||||
@@ -12,6 +12,7 @@ Regatta=Régate
|
||||
CourseArea=Zone de course
|
||||
BoatClass=Catégorie de bateau
|
||||
Fleet=Flotte
|
||||
RaceColumn=Colonne Course
|
||||
Race=Course
|
||||
Competitor=Concurrent
|
||||
Boat=Bateau
|
||||
@@ -39,7 +40,7 @@ BravoFix=Position Bravo
|
||||
BravoFixTrack=Enregistrement de position Bravo
|
||||
WindFix=Point vent
|
||||
Maneuver=Manœuvre
|
||||
WindStrengthInBeaufort=Force du vent sur l''échelle de Beaufort
|
||||
WindStrengthInBeaufort=Force du vent sur l'échelle de Beaufort
|
||||
Bft0=Calme
|
||||
Bft1=Très légère brise
|
||||
Bft2=Légère brise
|
||||
@@ -64,7 +65,7 @@ SpeedTenSecondsBeforeStart=Vitesse 10 s avant le départ
|
||||
RankAtFirstMark=Place à la première marque
|
||||
NumberOfManeuvers=Nombre de manœuvres
|
||||
NumberOfTacks=Nombre de virements
|
||||
NumberOfJibes=Nombre de changements d''amure
|
||||
NumberOfJibes=Nombre de changements d'amure
|
||||
NumberOfPenaltyCircles=Nombre de tours de pénalité
|
||||
RankGainsOrLosses=Pertes ou gains de place
|
||||
DistanceToStarboardSideAtStart=Distance à tribord (% de la longueur)
|
||||
@@ -87,7 +88,7 @@ PassingSide=Côté de passage
|
||||
TackAtStart=Virement au départ
|
||||
SpeedTenSecondsAfterStart=Vitesse 10 s après le départ
|
||||
RankThirtySecondsAfterStart=Place 30 s après le départ
|
||||
WindwardDistanceToAdvantageousEndOfLine=Distance au vent jusqu''au côté avantageux de la ligne
|
||||
WindwardDistanceToAdvantageousEndOfLine=Distance au vent jusqu'au côté avantageux de la ligne
|
||||
RankAfterHalfOfTheFirstLeg=Place après la moitié de la première portion de parcours
|
||||
AdvantageousEndOfLine=Côté avantageux de la ligne
|
||||
TimeSpentInSeconds=Temps mis en secondes
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ Regatta=レガッタ
|
||||
CourseArea=コースエリア
|
||||
BoatClass=艇種
|
||||
Fleet=フリート
|
||||
RaceColumn=レース列
|
||||
Race=レース
|
||||
Competitor=競技者
|
||||
Boat=艇
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ Regatta=Regata
|
||||
CourseArea=Área do percurso
|
||||
BoatClass=Classe de barcos
|
||||
Fleet=Frota
|
||||
RaceColumn=Coluna da corrida
|
||||
Race=Corrida
|
||||
Competitor=Competidor
|
||||
Boat=Barco
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ Regatta=Регата
|
||||
CourseArea=Зона гонки
|
||||
BoatClass=Класс лодок
|
||||
Fleet=Флот
|
||||
RaceColumn=Столбец гонки
|
||||
Race=Гонка
|
||||
Competitor=Участник
|
||||
Boat=Лодка
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ Regatta=比赛
|
||||
CourseArea=场地区域
|
||||
BoatClass=船只级别
|
||||
Fleet=船队
|
||||
RaceColumn=比赛轮次列
|
||||
Race=比赛轮次
|
||||
Competitor=参赛队
|
||||
Boat=船只
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ public enum BoatClassMasterdata {
|
||||
_470 ("470", true, 4.70, 1.68, BoatHullType.MONOHULL, true, "470er", "470M", "470W", "470 - M", "470 - W"),
|
||||
_5O5 ("5O5", true, 5.03, 1.88, BoatHullType.MONOHULL, true, "505", "5o5", "505er", "5o5er"),
|
||||
_5_5MR ("5.5mR", true, 9.50, 1.92, BoatHullType.MONOHULL, true, "5.5 Meter", "5.5 Metre", "5.5", "5.5M", "5.5-metre", "5.5 metre"),
|
||||
_6MR ("6mR", true, 11, 2.2, BoatHullType.MONOHULL, true, "6 Meter", "6 Metre", "6", "6M", "6-metre", "6 metre"),
|
||||
_8MR ("8mR", true, 9.62, 2.45, BoatHullType.MONOHULL, true, "8 Meter", "8 Metre", "8M", "8-metre"),
|
||||
A_CAT ("A-Catamaran", true, 5.49, 2.30, BoatHullType.CATAMARAN, false, "A-Cat", "ACat", "A-Class Catamaran"),
|
||||
ALBIN_EXPRESS ("Albin Express", true, 7.77, 2.50, BoatHullType.MONOHULL, false),
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.sap.sailing.domain.common;
|
||||
|
||||
public interface LegIdentifier extends RegattaAndRaceIdentifier {
|
||||
RaceIdentifier getRaceIdentifier();
|
||||
RegattaAndRaceIdentifier getRaceIdentifier();
|
||||
String getLegName();
|
||||
int getLegNumber();
|
||||
}
|
||||
|
||||
+2
-3
@@ -2,7 +2,7 @@ package com.sap.sailing.domain.common;
|
||||
|
||||
public class LegIdentifierImpl extends RegattaNameAndRaceName implements LegIdentifier {
|
||||
private static final long serialVersionUID = 3599904513673776450L;
|
||||
private RaceIdentifier raceIdentifier = null;
|
||||
private RegattaAndRaceIdentifier raceIdentifier = null;
|
||||
private String legName;
|
||||
|
||||
LegIdentifierImpl() {}
|
||||
@@ -14,11 +14,10 @@ public class LegIdentifierImpl extends RegattaNameAndRaceName implements LegIden
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaceIdentifier getRaceIdentifier() {
|
||||
public RegattaAndRaceIdentifier getRaceIdentifier() {
|
||||
return this.raceIdentifier;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getLegName() {
|
||||
return legName;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package com.sap.sailing.domain.common;
|
||||
|
||||
public enum ScoringSchemeType {
|
||||
// make sure to also add new ones to ScoringSchemeTypeFormatter
|
||||
LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL,HIGH_POINT_ESS_OVERALL_12, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN,
|
||||
LOW_POINT, LOW_POINT_WITH_AUTOMATIC_RDG, HIGH_POINT, HIGH_POINT_ESS_OVERALL,HIGH_POINT_ESS_OVERALL_12, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN,
|
||||
HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE, HIGH_POINT_WINNER_GETS_EIGHT,
|
||||
HIGH_POINT_WINNER_GETS_EIGHT_AND_INTERPOLATION, HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT, HIGH_POINT_FIRST_GETS_TWELVE_OR_EIGHT, HIGH_POINT_FIRST_GETS_TWELVE_OR_EIGHT_2017, LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07,
|
||||
LOW_POINT_LEAGUE_OVERALL, HIGH_POINT_MATCH_RACING, LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY
|
||||
|
||||
+14
@@ -1,10 +1,13 @@
|
||||
package com.sap.sailing.domain.persistence;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBObject;
|
||||
import com.sap.sailing.domain.anniversary.DetailedRaceInfo;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Event;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
@@ -181,4 +184,15 @@ public interface MongoObjectFactory {
|
||||
* objects obtained through {@link DomainObjectFactory#loadConnectivityParametersForRacesToRestore(Consumer<RaceTrackingConnectivityParameter>)}.
|
||||
*/
|
||||
void removeAllConnectivityParametersForRacesToRestore();
|
||||
|
||||
/**
|
||||
* Currently unused, meant to store determined Anniversaries related to bug4227
|
||||
*/
|
||||
void storeAnniversaryData(Map<Integer, DetailedRaceInfo> data);
|
||||
|
||||
/**
|
||||
* Currently unused, meant to store determined Anniversaries related to bug4227
|
||||
* @throws MalformedURLException
|
||||
*/
|
||||
Map<Integer, DetailedRaceInfo> getAnniversaryData() throws MalformedURLException;
|
||||
}
|
||||
|
||||
+5
@@ -107,4 +107,9 @@ public enum CollectionNames {
|
||||
* way by corresponding implementations of the RaceTrackingConnectivityParametersMongoHandler} interface.
|
||||
*/
|
||||
CONNECTIVITY_PARAMS_FOR_RACES_TO_BE_RESTORED,
|
||||
|
||||
/**
|
||||
* Contains the known anniversaries
|
||||
*/
|
||||
ANNIVERSARIES
|
||||
}
|
||||
|
||||
+53
@@ -1,12 +1,15 @@
|
||||
package com.sap.sailing.domain.persistence.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -18,6 +21,7 @@ import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.BasicDBObjectBuilder;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.DuplicateKeyException;
|
||||
import com.mongodb.MongoCommandException;
|
||||
@@ -63,6 +67,7 @@ import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnTimeFactorEvent;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorBravoMappingEventImpl;
|
||||
import com.sap.sailing.domain.anniversary.DetailedRaceInfo;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.ControlPoint;
|
||||
import com.sap.sailing.domain.base.ControlPointWithTwoMarks;
|
||||
@@ -89,6 +94,7 @@ import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.PassingInstruction;
|
||||
import com.sap.sailing.domain.common.Positioned;
|
||||
import com.sap.sailing.domain.common.RaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Wind;
|
||||
@@ -1630,4 +1636,51 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
database.getCollection(CollectionNames.CONNECTIVITY_PARAMS_FOR_RACES_TO_BE_RESTORED.name()).drop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeAnniversaryData(Map<Integer, DetailedRaceInfo> data) {
|
||||
try {
|
||||
DBCollection anniversarysStored = database.getCollection(CollectionNames.ANNIVERSARIES.name());
|
||||
for (Entry<Integer, DetailedRaceInfo> anniversary : data.entrySet()) {
|
||||
BasicDBObject currentProxy = new BasicDBObject("anniversary", anniversary.getKey().intValue());
|
||||
BasicDBObject newValue = new BasicDBObject("anniversary", anniversary.getKey().intValue());
|
||||
newValue.append("race", anniversary.getValue().getIdentifier().getRaceName());
|
||||
newValue.append("regatta", anniversary.getValue().getIdentifier().getRegattaName());
|
||||
newValue.append("leaderboardName", anniversary.getValue().getLeaderboardName());
|
||||
storeTimePoint(anniversary.getValue().getStartOfRace(), newValue, ("startOfRace"));
|
||||
newValue.append("eventID", anniversary.getValue().getEventID().toString());
|
||||
newValue.append("remoteUrl", anniversary.getValue().getRemoteUrl());
|
||||
anniversarysStored.update(currentProxy, newValue, true, false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, DetailedRaceInfo> getAnniversaryData() throws MalformedURLException {
|
||||
HashMap<Integer, DetailedRaceInfo> fromDb = new HashMap<>();
|
||||
DBCollection anniversarysStored = database.getCollection(CollectionNames.ANNIVERSARIES.name());
|
||||
DBCursor cursor = anniversarysStored.find();
|
||||
while (cursor.hasNext()) {
|
||||
DBObject toLoad = cursor.next();
|
||||
String leaderboardName = toLoad.get("leaderboardName").toString();
|
||||
String eventID = toLoad.get("eventID").toString();
|
||||
|
||||
TimePoint startOfRace = new MillisecondsTimePoint(((Number) toLoad.get("startOfRace")).longValue());
|
||||
String race = toLoad.get("race").toString();
|
||||
String regatta = toLoad.get("regatta").toString();
|
||||
Object rurl = toLoad.get("remoteUrl");
|
||||
final URL remoteUrlOrNull;
|
||||
if (rurl != null) {
|
||||
remoteUrlOrNull = new URL(rurl.toString());
|
||||
} else {
|
||||
remoteUrlOrNull = null;
|
||||
}
|
||||
DetailedRaceInfo loadedAnniversary = new DetailedRaceInfo(new RegattaNameAndRaceName(regatta, race), leaderboardName , startOfRace, UUID.fromString(eventID), remoteUrlOrNull);
|
||||
int anniversary = ((Number) toLoad.get("anniversary")).intValue();
|
||||
fromDb.put(anniversary, loadedAnniversary);
|
||||
}
|
||||
return fromDb;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ Export-Package: com.sap.sailing.domain.abstractlog,
|
||||
com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl,
|
||||
com.sap.sailing.domain.abstractlog.shared.analyzing,
|
||||
com.sap.sailing.domain.abstractlog.shared.events,
|
||||
com.sap.sailing.domain.anniversary,
|
||||
com.sap.sailing.domain.base,
|
||||
com.sap.sailing.domain.base.configuration,
|
||||
com.sap.sailing.domain.base.configuration.impl,
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.sap.sailing.domain.anniversary;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
/**
|
||||
* Reference to an "anniversary" race, such that it can be used to obtain a link to the race on its hosting server. For
|
||||
* this, the object hosts additional information about the leaderboard and the event.
|
||||
*/
|
||||
public class DetailedRaceInfo extends SimpleRaceInfo {
|
||||
private final String leaderboardName;
|
||||
private final UUID eventID;
|
||||
|
||||
public DetailedRaceInfo(RegattaAndRaceIdentifier identifier, String leaderboardName, TimePoint timePoint,
|
||||
UUID eventId, URL remoteUrl) {
|
||||
super(identifier, timePoint, remoteUrl);
|
||||
if (leaderboardName == null || eventId == null) {
|
||||
throw new IllegalStateException("DetailedRaceInfo Data is not allowed to contain any null values!");
|
||||
}
|
||||
this.leaderboardName = leaderboardName;
|
||||
this.eventID = eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor to easily switch remoteUrl value
|
||||
*/
|
||||
public DetailedRaceInfo(DetailedRaceInfo copy, URL remoteUrl) {
|
||||
this(copy.getIdentifier(), copy.getLeaderboardName(), copy.getStartOfRace(), copy.getEventID(), remoteUrl);
|
||||
}
|
||||
|
||||
public String getLeaderboardName() {
|
||||
return leaderboardName;
|
||||
}
|
||||
|
||||
public UUID getEventID() {
|
||||
return eventID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DetailedRaceInfo [identifier=" + getIdentifier() + ", leaderboardName=" + leaderboardName
|
||||
+ ", startOfRace=" + getStartOfRace() + ", eventID=" + eventID + "]";
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package com.sap.sailing.domain.anniversary;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
/**
|
||||
* Used to capture information about a race for use in an "anniversary" feature that allows us
|
||||
* to sort a set of races by their start time. Races are identified by a {@link RegattaAndRaceIdentifier}.
|
||||
* Their start time is recorded in the {@link #startOfRace} field which must not be {@code null}, and
|
||||
* the {@link URL} of the remote server reference is stored in {@link #remoteUrl}. Using clients shall
|
||||
* make sure that the same {@link URL} object is used for larger sets of objects of this type in order
|
||||
* not to waste memory on a massive replication of those equal {@link URL} objects identifying the same
|
||||
* remote server.
|
||||
*/
|
||||
public class SimpleRaceInfo {
|
||||
private final RegattaAndRaceIdentifier identifier;
|
||||
private final TimePoint startOfRace;
|
||||
private final URL remoteUrl;
|
||||
|
||||
/**
|
||||
* @param remoteUrl
|
||||
* use {@code null} to mean "local"; a local server does not necessarily know under which URL it is being
|
||||
* reached and therefore cannot provide this
|
||||
*/
|
||||
public SimpleRaceInfo(RegattaAndRaceIdentifier identifier, TimePoint startOfRace, URL remoteUrl) {
|
||||
if (identifier == null || startOfRace == null) {
|
||||
throw new IllegalStateException("SimpleRaceInfo Data is not allowed to contain any null values!");
|
||||
}
|
||||
this.identifier = identifier;
|
||||
this.startOfRace = startOfRace;
|
||||
this.remoteUrl = remoteUrl;
|
||||
}
|
||||
|
||||
public RegattaAndRaceIdentifier getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL used in a remote server reference through which the record was obtained; or {@code null} in
|
||||
* case the race identified by this object resides on the local server responding to a request.
|
||||
*/
|
||||
public URL getRemoteUrl() {
|
||||
return remoteUrl;
|
||||
}
|
||||
|
||||
public TimePoint getStartOfRace() {
|
||||
return startOfRace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((identifier == null) ? 0 : identifier.hashCode());
|
||||
result = prime * result + ((startOfRace == null) ? 0 : startOfRace.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
SimpleRaceInfo other = (SimpleRaceInfo) obj;
|
||||
if (identifier == null) {
|
||||
if (other.identifier != null)
|
||||
return false;
|
||||
} else if (!identifier.equals(other.identifier))
|
||||
return false;
|
||||
if (startOfRace == null) {
|
||||
if (other.startOfRace != null)
|
||||
return false;
|
||||
} else if (!startOfRace.equals(other.startOfRace))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -105,6 +105,32 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki
|
||||
assertEquals(competitors, rankedCompetitors);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutomaticRdgAdjustment() throws NoWindException {
|
||||
List<Competitor> competitors = createCompetitors(10);
|
||||
Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */4,
|
||||
new String[] { "Default" },
|
||||
/* medal */false, /* medal */ 0, "testOneStartedRaceWithDifferentScores",
|
||||
DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_WITH_AUTOMATIC_RDG));
|
||||
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
|
||||
TrackedRace f1 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors);
|
||||
RaceColumn f1Column = series.get(1).getRaceColumnByName("F1");
|
||||
RaceColumn f2Column = series.get(1).getRaceColumnByName("F2");
|
||||
RaceColumn f3Column = series.get(1).getRaceColumnByName("F3");
|
||||
RaceColumn f4Column = series.get(1).getRaceColumnByName("F4");
|
||||
f1Column.setTrackedRace(f1Column.getFleets().iterator().next(), f1);
|
||||
leaderboard.getScoreCorrection().setMaxPointsReason(competitors.get(3), f1Column, MaxPointsReason.RDG);
|
||||
leaderboard.getScoreCorrection().correctScore(competitors.get(3), f2Column, 7);
|
||||
assertEquals(7, leaderboard.getTotalPoints(competitors.get(3), f1Column, later), 0.0000001);
|
||||
leaderboard.getScoreCorrection().correctScore(competitors.get(3), f3Column, 5);
|
||||
assertEquals(6, leaderboard.getTotalPoints(competitors.get(3), f1Column, later), 0.0000001);
|
||||
leaderboard.getScoreCorrection().setMaxPointsReason(competitors.get(3), f4Column, MaxPointsReason.RDG);
|
||||
assertEquals(6, leaderboard.getTotalPoints(competitors.get(3), f1Column, later), 0.0000001);
|
||||
assertEquals(6, leaderboard.getTotalPoints(competitors.get(3), f4Column, later), 0.0000001);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regarding bug 912, test adding a disqualification in the middle, with a high-point scoring scheme, and check that
|
||||
* all competitors ranked worse advance by one, including getting <em>more</em> points due to the high-point scoring
|
||||
|
||||
+9
-10
@@ -71,10 +71,18 @@ public class RaceStartedAndFinishedReceiver extends AbstractReceiverWithQueue<IR
|
||||
final IRace race = event.getA();
|
||||
// An uninitialized race means that all timing data is not valid; we replace start of tracking,
|
||||
// end of tracking and start time received by null values.
|
||||
IStartStopData startEndRaceTimesData = event.getC();
|
||||
if (startEndRaceTimesData != null) {
|
||||
final long startTime = startEndRaceTimesData.getStartTime();
|
||||
TimePoint startOfRace = startTime == 0 ? null : getSimulator() == null ? new MillisecondsTimePoint(
|
||||
startTime) : getSimulator().advance(new MillisecondsTimePoint(startTime));
|
||||
trackedRace.setStartTimeReceived(startOfRace);
|
||||
// Note that end of race can't currently be set on a tracked race
|
||||
logger.info("Received race start time update for race "+race.getName()+": "+startTime);
|
||||
}
|
||||
if (!race.isInitialized()) {
|
||||
trackedRace.setStartOfTrackingReceived(null);
|
||||
trackedRace.setEndOfTrackingReceived(null);
|
||||
trackedRace.setStartTimeReceived(null);
|
||||
logger.info("Received isInitialized()==false for race "+race.getName());
|
||||
} else {
|
||||
IStartStopData startEndTrackingTimesData = event.getB();
|
||||
@@ -91,15 +99,6 @@ public class RaceStartedAndFinishedReceiver extends AbstractReceiverWithQueue<IR
|
||||
trackedRace.setEndOfTrackingReceived(endOfTracking);
|
||||
logger.info("Received tracking times update for race "+race.getName()+": "+startOfTracking+".."+endOfTracking);
|
||||
}
|
||||
IStartStopData startEndRaceTimesData = event.getC();
|
||||
if (startEndRaceTimesData != null) {
|
||||
final long startTime = startEndRaceTimesData.getStartTime();
|
||||
TimePoint startOfRace = startTime == 0 ? null : getSimulator() == null ? new MillisecondsTimePoint(
|
||||
startTime) : getSimulator().advance(new MillisecondsTimePoint(startTime));
|
||||
trackedRace.setStartTimeReceived(startOfRace);
|
||||
// Note that end of race can't currently be set on a tracked race
|
||||
logger.info("Received race start time update for race "+race.getName()+": "+startTime);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warning("Couldn't find tracked race for race " + event.getA().getName()
|
||||
|
||||
+3
@@ -55,6 +55,7 @@ import com.sap.sailing.domain.leaderboard.impl.LowPoint;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPointForLeagueOverallLeaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPointTieBreakBasedOnLastSeriesOnly;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPointWithAutomaticRDG;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPointWithEliminationsAndRoundsWinnerGets07;
|
||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
@@ -133,6 +134,8 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain
|
||||
return new LowPointForLeagueOverallLeaderboard();
|
||||
case LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY:
|
||||
return new LowPointTieBreakBasedOnLastSeriesOnly();
|
||||
case LOW_POINT_WITH_AUTOMATIC_RDG:
|
||||
return new LowPointWithAutomaticRDG();
|
||||
}
|
||||
throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name());
|
||||
}
|
||||
|
||||
+2
-2
@@ -57,16 +57,16 @@ public interface ScoreCorrection extends Serializable {
|
||||
* @param trackedRankProvider
|
||||
* can provide the tracked rank if needed; will only be called if there is no score correction for the
|
||||
* <code>timePoint</code> specified.
|
||||
* @param leaderboard TODO
|
||||
* @param timePoint
|
||||
* the time point for which to get the corrected score; score corrections have a validity time interval.
|
||||
* Only the last score correction valid at <code>timePoint</code> is considered.
|
||||
*
|
||||
* @param numberOfCompetitorsFetcher
|
||||
* can determine the number of competitors to use as the basis for penalty score calculation
|
||||
* ("max points") if needed
|
||||
*/
|
||||
Result getCorrectedScore(Callable<Integer> trackedRankProvider, Competitor competitor, RaceColumn raceColumn,
|
||||
TimePoint timePoint, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsFetcher, ScoringScheme scoringScheme);
|
||||
Leaderboard leaderboard, TimePoint timePoint, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsFetcher, ScoringScheme scoringScheme);
|
||||
|
||||
/**
|
||||
* @param timePoint
|
||||
|
||||
+11
-7
@@ -50,7 +50,6 @@ public interface ScoringScheme extends Serializable {
|
||||
* If the <code>competitor</code> has no {@link RaceColumn#getTrackedRace(Competitor) tracked race} in the column in which
|
||||
* the competitor participated, <code>null</code> is returned, meaning the competitor has no score assigned for that
|
||||
* race.
|
||||
* @param timePoint TODO
|
||||
*/
|
||||
Double getScoreForRank(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor,
|
||||
int rank, Callable<Integer> numberOfCompetitorsInRaceFetcher,
|
||||
@@ -62,16 +61,21 @@ public interface ScoringScheme extends Serializable {
|
||||
* or regatta.
|
||||
*
|
||||
* @param numberOfCompetitorsInLeaderboardFetcher
|
||||
* if it returns <code>null</code>, the caller cannot determine the number of competitors in the single race;
|
||||
* otherwise, this parameter tells the number of competitors in the same race as <code>competitor</code>,
|
||||
* not in the entire <code>raceColumn</code> (those may be more in case of split fleets). The scoring scheme
|
||||
* may use this number, if available, to infer a penalty score.
|
||||
* if it returns <code>null</code>, the caller cannot determine the number of competitors in the single
|
||||
* race; otherwise, this parameter tells the number of competitors in the same race as
|
||||
* <code>competitor</code>, not in the entire <code>raceColumn</code> (those may be more in case of split
|
||||
* fleets). The scoring scheme may use this number, if available, to infer a penalty score.
|
||||
* @param timePoint
|
||||
* an optional timePoint that may help the scheme to determine the penalty related to a certain point in time only.
|
||||
* an optional timePoint that may help the scheme to determine the penalty related to a certain point in
|
||||
* time only.
|
||||
* @param leaderboard
|
||||
* may be required in case a "penalty" such as a redress needs to inspect the scores of other race
|
||||
* columns as well; implementations need to take great care not to cause endless recursions by
|
||||
* naively asking the leaderboard for scores which would recurse into this method
|
||||
*/
|
||||
Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason,
|
||||
Integer numberOfCompetitorsInRace, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher,
|
||||
TimePoint timePoint);
|
||||
TimePoint timePoint, Leaderboard leaderboard);
|
||||
|
||||
/**
|
||||
* @param competitor1Scores scores of the first competitor, in the order of race columns in the leaderboard
|
||||
|
||||
+3
-3
@@ -385,7 +385,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
public Double getTotalPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) {
|
||||
return getScoreCorrection().getCorrectedScore(
|
||||
()->getTrackedRank(competitor, raceColumn, timePoint), competitor,
|
||||
raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore();
|
||||
raceColumn, this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -627,7 +627,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
Set<RaceColumn> discardedRaceColumns) throws NoWindException {
|
||||
Callable<Integer> trackedRankProvider = ()->getTrackedRank(competitor, race, timePoint);
|
||||
final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race,
|
||||
timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
|
||||
this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
|
||||
boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns);
|
||||
final Double correctedScore = correctedResults.getCorrectedScore();
|
||||
return new EntryImpl(trackedRankProvider, correctedScore, ()->correctedResults.getUncorrectedScore(),
|
||||
@@ -691,7 +691,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
|
||||
for (final Competitor competitor : getCompetitors()) {
|
||||
Callable<Integer> trackedRankProvider = ()->getTrackedRank(competitor, raceColumn, timePoint);
|
||||
final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn,
|
||||
timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
|
||||
this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
|
||||
Set<RaceColumn> discardedRacesForCompetitor = discardedRaces.get(competitor);
|
||||
if (discardedRacesForCompetitor == null) {
|
||||
discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ public class HighPoint extends AbstractScoringSchemeImpl {
|
||||
|
||||
@Override
|
||||
public Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason, Integer numberOfCompetitorsInRace,
|
||||
NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
|
||||
NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint, Leaderboard leaderboard) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -5,6 +5,7 @@ import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
@@ -36,7 +37,7 @@ public class HighPointFirstGets12Or8AndLastBreaksTie2017 extends AbstractHighPoi
|
||||
|
||||
@Override
|
||||
public Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason,
|
||||
Integer numberOfCompetitorsInRace, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
|
||||
Integer numberOfCompetitorsInRace, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint, Leaderboard leaderboard) {
|
||||
Double result;
|
||||
/**
|
||||
* 1. Get all competitors without a max point reason.
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util;
|
||||
@@ -24,7 +25,7 @@ public class HighPointFirstGets1LastBreaksTie extends HighPointFirstGetsFixedSco
|
||||
|
||||
@Override
|
||||
public Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason, Integer numberOfCompetitorsInRace,
|
||||
NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
|
||||
NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint, Leaderboard leaderboard) {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ public class LowPoint extends AbstractScoringSchemeImpl {
|
||||
|
||||
@Override
|
||||
public Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason,
|
||||
Integer numberOfCompetitorsInRace, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) {
|
||||
Integer numberOfCompetitorsInRace, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint, Leaderboard leaderboard) {
|
||||
Double result;
|
||||
if (numberOfCompetitorsInRace == null || raceColumn.hasSplitFleetContiguousScoring()) {
|
||||
result = (double) (numberOfCompetitorsInLeaderboardFetcher.getNumberOfCompetitorsInLeaderboard()+1);
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.sap.sailing.domain.leaderboard.impl;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
public class LowPointWithAutomaticRDG extends LowPoint {
|
||||
private static final long serialVersionUID = -6609749761795712461L;
|
||||
|
||||
/**
|
||||
* The penalty score will be that calculated by the default in {@link LowPoint}, except for
|
||||
* {@link MaxPointsReason#RDG RDG} where all columns where the {@code competitor} has a
|
||||
* non-{@link MaxPointsReason#RDG RDG} score are averaged (including those discarded) to figure out the score to be
|
||||
* assigned to the {@link MaxPointsReason#RDG RDG} redress.
|
||||
*/
|
||||
@Override
|
||||
public Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason,
|
||||
Integer numberOfCompetitorsInRace,
|
||||
NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint,
|
||||
Leaderboard leaderboard) {
|
||||
final Double result;
|
||||
if (maxPointsReason == MaxPointsReason.RDG) {
|
||||
double scoreSum = 0;
|
||||
int numberOfScores = 0;
|
||||
for (final RaceColumn rc : leaderboard.getRaceColumns()) {
|
||||
if (rc != raceColumn && leaderboard.getMaxPointsReason(competitor, rc, timePoint) != MaxPointsReason.RDG) {
|
||||
final Double totalPointsInRC = leaderboard.getTotalPoints(competitor, rc, timePoint);
|
||||
if (totalPointsInRC != null) {
|
||||
scoreSum += totalPointsInRC;
|
||||
numberOfScores++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (numberOfScores == 0) {
|
||||
// if only RDG races exist, leave null
|
||||
result = null;
|
||||
} else {
|
||||
result = scoreSum / (double) numberOfScores;
|
||||
}
|
||||
} else {
|
||||
result = super.getPenaltyScore(raceColumn, competitor, maxPointsReason, numberOfCompetitorsInRace,
|
||||
numberOfCompetitorsInLeaderboardFetcher, timePoint, leaderboard);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScoringSchemeType getType() {
|
||||
return ScoringSchemeType.LOW_POINT_WITH_AUTOMATIC_RDG;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -370,7 +370,7 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
|
||||
*/
|
||||
@Override
|
||||
public Result getCorrectedScore(final Callable<Integer> trackedRankProvider, final Competitor competitor,
|
||||
final RaceColumn raceColumn, final TimePoint timePoint,
|
||||
final RaceColumn raceColumn, Leaderboard leaderboard, final TimePoint timePoint,
|
||||
final NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher,
|
||||
final ScoringScheme scoringScheme) {
|
||||
Double result;
|
||||
@@ -390,7 +390,7 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
|
||||
if (correctedNonMaxedScore == null) {
|
||||
result = scoringScheme.getPenaltyScore(raceColumn, competitor, maxPointsReason.getMaxPointsReason(),
|
||||
getNumberOfCompetitorsInRace(raceColumn, competitor, numberOfCompetitorsInLeaderboardFetcher),
|
||||
numberOfCompetitorsInLeaderboardFetcher, timePoint);
|
||||
numberOfCompetitorsInLeaderboardFetcher, timePoint, leaderboard);
|
||||
} else {
|
||||
result = correctedNonMaxedScore;
|
||||
}
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ public abstract class AbstractTrackedRegattaAndRaceObserver implements TrackedRe
|
||||
trackedRegatta.addRaceListener(raceListener);
|
||||
}
|
||||
|
||||
public void raceRemoved(TrackedRace trackedRace) {
|
||||
public synchronized void raceRemoved(TrackedRace trackedRace) {
|
||||
remove(trackedRace.getRaceIdentifier());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="#008ECB" d="M12,6 C18.628,6 24,11.3723636 24,18 L14.4469091,18 C14.7512727,17.3749091 14.8014545,16.6574545 14.5978182,16.0014545 C17.4652213,12.5632485 18.9778274,10.7116121 19.1356364,10.4465455 C19.3723498,10.0489455 18.9483532,9.41421786 18.3643636,9.67490909 C17.9750373,9.84870324 16.1231585,11.3611881 12.8087273,14.2123636 C11.8752727,13.9225455 10.816,14.1472727 10.0767273,14.8865455 C9.23454545,15.7283636 9.06036364,16.9854545 9.55309091,18 L0,18 C0,11.3723636 5.37236364,6 12,6 Z M12,12.4538182 L12,7.63636364 C6.83309091,7.63636364 2.54945455,11.4174545 1.76472727,16.3636364 L7.66618182,16.3636364 C7.88981818,14.1676364 9.74472727,12.4538182 12,12.4538182 L12,12.4538182 Z M11.1589091,15.9690909 C11.6232727,15.5047273 12.3763636,15.5047273 12.8407273,15.9690909 C13.3054545,16.4334545 13.3054545,17.1869091 12.8407273,17.6512727 C12.3763636,18.116 11.6232727,18.116 11.1589091,17.6512727 C10.6941818,17.1869091 10.6941818,16.4334545 11.1589091,15.9690909 L11.1589091,15.9690909 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
+1
@@ -34,6 +34,7 @@ public class BoatClassImageResolver {
|
||||
boatClassIconsMap.put(BoatClassMasterdata._49ER.getDisplayName(), imageResources._49erIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata._5O5.getDisplayName(), imageResources._505mIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata._5_5MR.getDisplayName(), imageResources._5_5MRIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata._6MR.getDisplayName(), imageResources._6MRIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata._8MR.getDisplayName(), imageResources._8MRIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.A_CAT.getDisplayName(), imageResources.A_CatIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.ALBIN_EXPRESS.getDisplayName(), imageResources.AlbinExpressIcon());
|
||||
|
||||
+3
@@ -40,6 +40,9 @@ public interface BoatClassImageResources extends ClientBundle {
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/5.5mR.png")
|
||||
ImageResource _5_5MRIcon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/6MR.png")
|
||||
ImageResource _6MRIcon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/8MR.png")
|
||||
ImageResource _8MRIcon();
|
||||
|
||||
|
||||
+1
@@ -50,6 +50,7 @@
|
||||
}
|
||||
.regattaheader_content_details {
|
||||
overflow: hidden;
|
||||
min-height: 1.133333333333333em;
|
||||
}
|
||||
.regattaheader_content_details_item {
|
||||
float: left;
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ public class DesktopStatisticsBoxView extends Composite implements StatisticsBox
|
||||
StatisticsBoxResources.INSTANCE.css().ensureInjected();
|
||||
initWidget(uiBinder.createAndBindUi(this));
|
||||
titleUi.setInnerText(title);
|
||||
if(embedded) {
|
||||
if (embedded) {
|
||||
addStyleName(StatisticsBoxResources.INSTANCE.css().box_embedded());
|
||||
}
|
||||
setVisible(false);
|
||||
|
||||
+7
@@ -10,6 +10,13 @@
|
||||
The statistics can be found at the end of the list of events for a year.
|
||||
To see statistics of a past year, you need to expand the list of events for that year.
|
||||
</li>
|
||||
<li>The simulation overlay is now also available prior to the start of a live race. It can
|
||||
therefore, e.g., be used to "predict" the best course on the first upwind leg based on
|
||||
the latest wind measurements.
|
||||
</li>
|
||||
<li>In several places you can now find statistics regarding the highest speed and the competitor that produced it (fastest competitor).
|
||||
These statistics are available for events and regattas as well as in the yearly statistics.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h5 class="articleSubheadline">July 2017</h5>
|
||||
|
||||
+1
-2
@@ -50,9 +50,8 @@ img.statisticsbox_content_item_icon {
|
||||
-moz-transform: translateY(-50%);
|
||||
-ms-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
width: 3.1em;
|
||||
text-align: center;
|
||||
padding: 0.230769230769231em 0;
|
||||
padding: 0.230769230769231em;
|
||||
background: #ebebeb;
|
||||
border: 1px solid #dedede;
|
||||
border-radius: 0.384615384615385em;
|
||||
|
||||
+1
-1
@@ -615,7 +615,7 @@ public class RaceContext {
|
||||
* {@link RaceViewState#RUNNING}, false otherwise
|
||||
*/
|
||||
public boolean isLive() {
|
||||
return getLiveRaceViewState() == RaceViewState.RUNNING;
|
||||
return getLiveRaceViewState() == RaceViewState.RUNNING || getLiveRaceViewState() == RaceViewState.FINISHING;
|
||||
}
|
||||
|
||||
public Collection<SimpleCompetitorDTO> getCompetitors() {
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@ package com.sap.sailing.gwt.home.shared.partials.statistics;
|
||||
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_COMPATITORS_COUNT;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_FASTEST_SAILOR;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_MAX_SPEED;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_RACES_COUNT;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_RAW_GPS_FIX;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_REGATTAS_FOUGHT;
|
||||
@@ -37,6 +38,6 @@ public class EventStatisticsBoxPresenter extends AbstractStatisticsBoxPresenter
|
||||
addItemWithCompactFormat(ICON_WIND_FIX, MSG.numberWindFixes(), statistics.getNumberOfWindFixes());
|
||||
addItemWithCompactFormat(ICON_SUM_MILES, MSG.sailedMiles(), statistics.getTotalDistanceTraveled() == null ? null : statistics.getTotalDistanceTraveled().getSeaMiles());
|
||||
addCompetitorItem(ICON_FASTEST_SAILOR, MSG.fastestSailor(), statistics.getFastestCompetitor());
|
||||
addKnotsItem(ICON_FASTEST_SAILOR, MSG.highestSpeed(), statistics.getFastestCompetitorSpeedInKnots());
|
||||
addKnotsItem(ICON_MAX_SPEED, MSG.highestSpeed(), statistics.getFastestCompetitorSpeedInKnots());
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -6,6 +6,7 @@ public interface StatisticsBoxConstants {
|
||||
public static final String ICON_RACES_COUNT = "images/mobile/icon_racesCount.svg";
|
||||
public static final String ICON_TRACKED_COUNT = "images/mobile/icon_trackedCount.svg";
|
||||
public static final String ICON_FASTEST_SAILOR = "images/mobile/fastest_sailor.svg";
|
||||
public static final String ICON_MAX_SPEED = "images/mobile/max_speed.svg";
|
||||
public static final String ICON_RAW_GPS_FIX = "images/mobile/raw_gps_fixes.svg";
|
||||
public static final String ICON_WIND_FIX = "images/mobile/strongest_wind.svg";
|
||||
public static final String ICON_SUM_MILES = "images/mobile/sum_miles.svg";
|
||||
|
||||
+10
-2
@@ -1,6 +1,14 @@
|
||||
package com.sap.sailing.gwt.home.shared.partials.statistics;
|
||||
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.*;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_COMPATITORS_COUNT;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_FASTEST_SAILOR;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_MAX_SPEED;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_RACES_COUNT;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_RAW_GPS_FIX;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_REGATTAS_FOUGHT;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_SUM_MILES;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_TRACKED_COUNT;
|
||||
import static com.sap.sailing.gwt.home.shared.partials.statistics.StatisticsBoxConstants.ICON_WIND_FIX;
|
||||
|
||||
import com.sap.sailing.gwt.home.communication.eventlist.EventListYearDTO;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
@@ -23,6 +31,6 @@ public class YearStatisticsBoxPresenter extends AbstractStatisticsBoxPresenter {
|
||||
addItemWithCompactFormat(ICON_WIND_FIX, MSG.numberWindFixes(), statistics.getNumberOfWindFixes());
|
||||
addItemWithCompactFormat(ICON_SUM_MILES, MSG.sailedMiles(), statistics.getDistanceTraveled().getNauticalMiles());
|
||||
addCompetitorItem(ICON_FASTEST_SAILOR, MSG.fastestSailor(), statistics.getFastestCompetitor());
|
||||
addKnotsItem(ICON_FASTEST_SAILOR, MSG.highestSpeed(), statistics.getFastestCompetitorSpeedInKnots());
|
||||
addKnotsItem(ICON_MAX_SPEED, MSG.highestSpeed(), statistics.getFastestCompetitorSpeedInKnots());
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -436,6 +436,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
|
||||
String scoringSchemeLowPointForLeagueOverallLeaderboardDescription();
|
||||
String scoringSchemeLowPointTieBreakBasedOnLastSeriesOnly();
|
||||
String scoringSchemeLowPointTieBreakBasedOnLastSeriesOnlyDescription();
|
||||
String scoringSchemeLowPointWithAutomaticRdg();
|
||||
String scoringSchemeHighPointEssOverall12();
|
||||
String scoringSchemeHighPointEssOverall12Description();
|
||||
String scoringSchemeHighPointFirstGetsTwelveOrEight();
|
||||
|
||||
+1
@@ -449,6 +449,7 @@ scoringSchemeLowPointForLeagueOverallLeaderboard=Low Point, ties broken by act p
|
||||
scoringSchemeLowPointForLeagueOverallLeaderboardDescription=Instead of the usual count-back system used for low point schemes to break ties, competitors with equal point sums are compared by comparing the sum of the points they scored across all acts. If this still leaves a tie, the tie is broken by the last act.
|
||||
scoringSchemeLowPointTieBreakBasedOnLastSeriesOnly=Low Point, ties broken based on last series only
|
||||
scoringSchemeLowPointTieBreakBasedOnLastSeriesOnlyDescription=Instead of the usual count-back system for low point schemes to break ties, the score of the final series with no races excluded is considered if two competitors have equal scores.
|
||||
scoringSchemeLowPointWithAutomaticRdg=Low Point System, RDG gets default score based on average of all non-RDG scores
|
||||
scoringSchemeHighPointFirstGetsOne=High Point, winner gets 1 point
|
||||
scoringSchemeHighPointFirstGetsTen=High Point, winner gets 10 points
|
||||
scoringSchemeHighPointFirstGetsTenOrEight=High Point, winner gets 10 points (or 8)
|
||||
|
||||
+1
@@ -448,6 +448,7 @@ scoringSchemeLowPointForLeagueOverallLeaderboard=Low Point System, bei Punktegle
|
||||
scoringSchemeLowPointForLeagueOverallLeaderboardDescription=Bei Punktegleichstand wird die Punktesumme über alle Spieltage verglichen; weniger ist besser. Bei Gleichstand entscheidet das Abschneiden am letzten Spieltag.
|
||||
scoringSchemeLowPointTieBreakBasedOnLastSeriesOnly=Low Point System, bei Punktegleichstand zählt letzten Serie
|
||||
scoringSchemeLowPointTieBreakBasedOnLastSeriesOnlyDescription=Bei Punktegleichstand entscheidet die Punktesumme aller (auch der gestrichenen) Wettfahrten der letzten Serie
|
||||
scoringSchemeLowPointWithAutomaticRdg=Low Point System, RDG erhält Durchschnitt aller nicht-RDG-Wertungen als Vorschlag
|
||||
scoringSystem=Bepunktungssystem
|
||||
createFlexibleLeaderboard=Flexible Rangliste anlegen
|
||||
createRegattaLeaderboard=Rangliste für Regatta anlegen
|
||||
|
||||
+7
-5
@@ -1099,9 +1099,9 @@ leaderboardGroupsOfSelectedEvent=Grupos de tablas de clasificación asignados a
|
||||
addRaces=Añadir carreras
|
||||
withNamePrefix=con prefijo de nombre
|
||||
pleaseSelectASeriesFirst=Seleccione primero una serie
|
||||
upcommingEventStartsInDays=en {0,number} días
|
||||
upcommingEventStartsInDays[one]=mañana
|
||||
upcommingEventStartsInDays[none]=hoy
|
||||
upcomingEventStartsInDays=en {0,number} días
|
||||
upcomingEventStartsInDays[one]=mañana
|
||||
upcomingEventStartsInDays[none]=hoy
|
||||
sharingMailTooltip=Compartir por correo
|
||||
sharingTwitterTooltip=Compartir en Twitter
|
||||
sharingFacebookTooltip=Compartir en Facebook
|
||||
@@ -1808,10 +1808,10 @@ settingsRemoved=Opciones por defecto restablecidas
|
||||
settingsRemovedError=No se han podido restablecer las opciones por defecto
|
||||
userSettingsFilter=Filtro de opciones
|
||||
requiresRegattaRaceAndLeaderboard=Esta página requiere un nombre de regata, un nombre de carrera y un nombre de tabla de clasificación válidos.
|
||||
couldNotFindRaceInRegatte=No se ha podido obtener la carrera con el nombre {0} para la regata con el nombre {1}
|
||||
couldNotFindRaceInRegatta=No se ha podido obtener la carrera con el nombre {0} para la regata con el nombre {1}
|
||||
algorithmTimeOutText=fuera de límites
|
||||
mixedLegText=viento ambiguo
|
||||
pathCanvasOverlayTitle=Ruta a {0} puntos. {1} millas naúticas en {2}
|
||||
pathCanvasOverlayTitle=Ruta a {0} puntos. {1} NM en {2}
|
||||
windSpeedInKnots=Velocidad del viento en nudos
|
||||
activeFilters=Activar filtro(s): {0}
|
||||
errorLoadingRegattasForLeaderboardGroup=Error al intentar cargar las regatas correspondientes para el grupo de tablas de clasificación {0}: {1}
|
||||
@@ -1851,3 +1851,5 @@ errorStartingTracking=Error al iniciar el rastreo {0}: {1}
|
||||
errorLoadingLeaderBoardByEvent=Error al intentar cargar la tabla de clasificación. O bien el evento no se ha podido asociar con la regata o no existen carreras rastreadas.
|
||||
errorLoadingEvent=Error al cargar el evento con ID {0} : {1}
|
||||
errorLoadingRegattaStructure=Error al intentar cargar regatas para el evento con ID {0} : {1}
|
||||
showEstimatedDuration=Mostrar duración estimada
|
||||
estimatedDuration=Duración estimada:
|
||||
|
||||
+7
-5
@@ -1099,9 +1099,9 @@ leaderboardGroupsOfSelectedEvent=Groupe de palmarès affectés à la manifestati
|
||||
addRaces=Ajouter des courses
|
||||
withNamePrefix=avec préfixe de nom
|
||||
pleaseSelectASeriesFirst=Sélectionnez d''abord une série.
|
||||
upcommingEventStartsInDays=dans {0,number} jours
|
||||
upcommingEventStartsInDays[one]=demain
|
||||
upcommingEventStartsInDays[none]=aujourd''hui
|
||||
upcomingEventStartsInDays=dans {0,number} jours
|
||||
upcomingEventStartsInDays[one]=demain
|
||||
upcomingEventStartsInDays[none]=aujourd''hui
|
||||
sharingMailTooltip=Partager par e-mail
|
||||
sharingTwitterTooltip=Partager sur Twitter
|
||||
sharingFacebookTooltip=Partager sur Facebook
|
||||
@@ -1808,10 +1808,10 @@ settingsRemoved=Options par défaut restaurées
|
||||
settingsRemovedError=Impossible de restaurer les options par défaut
|
||||
userSettingsFilter=Filtre d''options
|
||||
requiresRegattaRaceAndLeaderboard=Cette page requiert un nom de régate, un nom de course et un nom de palmarès valides.
|
||||
couldNotFindRaceInRegatte=Impossible de trouver une course nommée {0} pour une régate nommée {1}.
|
||||
couldNotFindRaceInRegatta=Impossible de trouver une course nommée {0} pour une régate nommée {1}.
|
||||
algorithmTimeOutText=Hors portée
|
||||
mixedLegText=Vent ambigu
|
||||
pathCanvasOverlayTitle=Chemin à {0} points. {1} nmi dans {2}
|
||||
pathCanvasOverlayTitle=Chemin à {0} points. {1} NM dans {2}
|
||||
windSpeedInKnots=Vitesse du vent en noeuds
|
||||
activeFilters=Filtre(s) actif(s) : {0}
|
||||
errorLoadingRegattasForLeaderboardGroup=Erreur lors de la tentative de chargement des régates correspondantes pour le groupe de palmarès {0} : {1}
|
||||
@@ -1851,3 +1851,5 @@ errorStartingTracking=Échec du lancement du suivi {0} : {1}
|
||||
errorLoadingLeaderBoardByEvent=Erreur lors de la tentative de chargement du palmarès. Soit l''événement n''a pas pu être associé à la régate, soit aucune course suivie n''existe.
|
||||
errorLoadingEvent=Erreur lors de la tentative de chargement de l''événement ayant l''ID {0} : {1}
|
||||
errorLoadingRegattaStructure=Erreur lors de la tentative de chargement de régates pour l''événement ayant l''ID {0} : {1}
|
||||
showEstimatedDuration=Afficher la durée estimée
|
||||
estimatedDuration=Durée estimée :
|
||||
|
||||
+6
-4
@@ -1099,9 +1099,9 @@ leaderboardGroupsOfSelectedEvent=イベントに割当済のリーダーボー
|
||||
addRaces=レース追加
|
||||
withNamePrefix=- 名称接頭辞:
|
||||
pleaseSelectASeriesFirst=最初にシリーズを選択してください
|
||||
upcommingEventStartsInDays={0,number} 日間
|
||||
upcommingEventStartsInDays[one]=明日
|
||||
upcommingEventStartsInDays[none]=今日
|
||||
upcomingEventStartsInDays={0,number} 日間
|
||||
upcomingEventStartsInDays[one]=明日
|
||||
upcomingEventStartsInDays[none]=今日
|
||||
sharingMailTooltip=メールによるシェア
|
||||
sharingTwitterTooltip=Twitter でシェア
|
||||
sharingFacebookTooltip=Facebook でシェア
|
||||
@@ -1808,7 +1808,7 @@ settingsRemoved=デフォルト設定が復元されました
|
||||
settingsRemovedError=デフォルト設定を復元できませんでした
|
||||
userSettingsFilter=設定フィルタ
|
||||
requiresRegattaRaceAndLeaderboard=このページには有効なレガッタ名、レース名、およびリーダーボード名が必要です。
|
||||
couldNotFindRaceInRegatte=名前 {1} のレガッタに対して名前 {0} のレースが取得できませんでした
|
||||
couldNotFindRaceInRegatta=名前 {1} のレガッタに対して名前 {0} のレースが取得できませんでした
|
||||
algorithmTimeOutText=禁止区域
|
||||
mixedLegText=不明瞭な風
|
||||
pathCanvasOverlayTitle={0} ポイントでの経路。{1} 海里 ({2})
|
||||
@@ -1851,3 +1851,5 @@ errorStartingTracking={0} の追跡の開始に失敗: {1}
|
||||
errorLoadingLeaderBoardByEvent=リーダーボードをロードしようとしてエラーが発生しました。イベントをレガッタに関連付けることができなかったか、追跡されているレースがありません。
|
||||
errorLoadingEvent=ID {0} のイベントをロードしようとしてエラーが発生: {1}
|
||||
errorLoadingRegattaStructure=ID {0} のイベントに対してレガッタをロードしようとしてエラーが発生: {1}
|
||||
showEstimatedDuration=推定持続時間の表示
|
||||
estimatedDuration=推定持続時間:
|
||||
|
||||
+9
-7
@@ -1099,9 +1099,9 @@ leaderboardGroupsOfSelectedEvent=Grupos de painéis de classificação atribuíd
|
||||
addRaces=Adicionar corridas
|
||||
withNamePrefix=com prefixo do nome
|
||||
pleaseSelectASeriesFirst=Selecione primeiro uma série.
|
||||
upcommingEventStartsInDays=em {0,número} dias
|
||||
upcommingEventStartsInDays[one]=amanhã
|
||||
upcommingEventStartsInDays[none]=hoje
|
||||
upcomingEventStartsInDays=em {0,número} dias
|
||||
upcomingEventStartsInDays[one]=amanhã
|
||||
upcomingEventStartsInDays[none]=hoje
|
||||
sharingMailTooltip=Compartilhar por e-mail
|
||||
sharingTwitterTooltip=Compartilhar no Twitter
|
||||
sharingFacebookTooltip=Compartilhar no Facebook
|
||||
@@ -1808,10 +1808,10 @@ settingsRemoved=Configurações padrão restauradas
|
||||
settingsRemovedError=Não foi possível restaurar configurações padrão
|
||||
userSettingsFilter=Filtro de configurações
|
||||
requiresRegattaRaceAndLeaderboard=Esta página necessita de um nome de regata, um nome de corrida e um nome de painel de classificação válidos.
|
||||
couldNotFindRaceInRegatte=Não foi possível obter uma corrida com o nome {0} para uma regata com o nome {1}
|
||||
couldNotFindRaceInRegatta=Não foi possível obter uma corrida com o nome {0} para uma regata com o nome {1}
|
||||
algorithmTimeOutText=fora do alcance
|
||||
mixedLegText=vento indefinido
|
||||
pathCanvasOverlayTitle=Caminho em {0} pontos {1} mn em {2}
|
||||
pathCanvasOverlayTitle=Caminho em {0} pontos {1} NM em {2}
|
||||
windSpeedInKnots=Velocidade do vento em nós
|
||||
activeFilters=Filtros ativos: {0}
|
||||
errorLoadingRegattasForLeaderboardGroup=Erro ao tentar carregar regatas correspondentes para grupo de painéis de classificação {0}: {1}
|
||||
@@ -1825,8 +1825,8 @@ errorLoadingRaceNames=Falha ao ler informação de corridas!\r\n {0}
|
||||
errorLoadingCompetitors=Falha ao ler informação do competidor!\r\n {0}
|
||||
errorLoadingTotalTime=Falha ao ler tempo total\r\n {0}
|
||||
errorUpdatingCarriedPoints=Erro ao tentar atualizar valor de transferência para competidor {0} no painel de classificação {1}: {2} \nPoderá ser necessário atualizar sua visão.
|
||||
errorUpdatingLeaderboardScore=Erro ao tentar atualizar correção de pontuação para competidor {0} no painel de classificação {1} para corrida {2}: {3} \nPoderá ser necessário atualizar sua visão.");
|
||||
errorUpdatingLeaderboardScoreCorrection=Erro ao tentar atualizar correção de pontuação para: {0} \nPoderá ser necessário atualizar sua visão.");
|
||||
errorUpdatingLeaderboardScore=Erro ao tentar atualizar correção de pontuação para competidor {0} no painel de classificação {1} para corrida {2}: {3} \nPoderá ser necessário atualizar sua visão.
|
||||
errorUpdatingLeaderboardScoreCorrection=Erro ao tentar atualizar correção de pontuação para: {0} \nPoderá ser necessário atualizar sua visão.
|
||||
errorUpdatingMaxPointsReason=Erro ao tentar atualizar motivo para máximo de pontos para competidor {0} no painel de classificação {1}: {2} \nPoderá ser necessário atualizar sua visão.
|
||||
errorUpdatingDisplayNames=Erro ao tentar atualizar nome de exibição para competidor {0} no painel de classificação {1}: {2} \nPoderá ser necessário atualizar sua visão.
|
||||
reportError=Erro ao gravar informação de correção de pontuação: {0}
|
||||
@@ -1851,3 +1851,5 @@ errorStartingTracking=Falha ao iniciar rastreamento {0}: {1}
|
||||
errorLoadingLeaderBoardByEvent=Erro ao tentar carregar painel de classificação. Ou não foi possível associar o evento à regata ou não existem corridas rastreadas.
|
||||
errorLoadingEvent=Erro ao tentar carregar evento com o ID {0}: {1}
|
||||
errorLoadingRegattaStructure=Erro ao tentar carregar regatas para evento com o ID {0}: {1}
|
||||
showEstimatedDuration=Exibir duração estimada
|
||||
estimatedDuration=duração estimada:
|
||||
|
||||
+6
-4
@@ -1099,9 +1099,9 @@ leaderboardGroupsOfSelectedEvent=Присвоенные событию груп
|
||||
addRaces=Добавить гонки
|
||||
withNamePrefix=с префиксом названия
|
||||
pleaseSelectASeriesFirst=Сначала выберите серию.
|
||||
upcommingEventStartsInDays=Через {0,number} дн.
|
||||
upcommingEventStartsInDays[one]=завтра
|
||||
upcommingEventStartsInDays[none]=сегодня
|
||||
upcomingEventStartsInDays=Через {0,number} дн.
|
||||
upcomingEventStartsInDays[one]=завтра
|
||||
upcomingEventStartsInDays[none]=сегодня
|
||||
sharingMailTooltip=Поделиться по почте
|
||||
sharingTwitterTooltip=Поделиться в Twitter
|
||||
sharingFacebookTooltip=Поделиться в Facebook
|
||||
@@ -1808,7 +1808,7 @@ settingsRemoved=Восстановлены настройки по умолча
|
||||
settingsRemovedError=Не удалось восстановить настройки по умолчанию
|
||||
userSettingsFilter=Фильтр настроек
|
||||
requiresRegattaRaceAndLeaderboard=Для этой страницы требуются действительные названия регаты, гонки и таблицы лидеров.
|
||||
couldNotFindRaceInRegatte=Не удалось получить гонку с названием {0} для регаты с названием {1}
|
||||
couldNotFindRaceInRegatta=Не удалось получить гонку с названием {0} для регаты с названием {1}
|
||||
algorithmTimeOutText=вне ограничений
|
||||
mixedLegText=неопределенный ветер
|
||||
pathCanvasOverlayTitle=Путь по {0} точкам. {1} мор. миль в {2}
|
||||
@@ -1851,3 +1851,5 @@ errorStartingTracking=Не удалось начать отслеживание
|
||||
errorLoadingLeaderBoardByEvent=Ошибка при попытке загрузить таблицу лидеров. Не удалось связать событие с регатой либо нет отслеживаемых гонок.
|
||||
errorLoadingEvent=Ошибка при попытке загрузить событие с ид. {0} : {1}
|
||||
errorLoadingRegattaStructure=Ошибка при попытке загрузить регаты для события с ид. {0} : {1}
|
||||
showEstimatedDuration=Показать оценку длительности
|
||||
estimatedDuration=оценка длительности:
|
||||
|
||||
+7
-5
@@ -1099,9 +1099,9 @@ leaderboardGroupsOfSelectedEvent=分配到活动的积分榜组
|
||||
addRaces=添加比赛轮次
|
||||
withNamePrefix=含名称前缀
|
||||
pleaseSelectASeriesFirst=请先选择系列。
|
||||
upcommingEventStartsInDays={0,number} 天后
|
||||
upcommingEventStartsInDays[one]=明天
|
||||
upcommingEventStartsInDays[none]=今天
|
||||
upcomingEventStartsInDays={0,number} 天后
|
||||
upcomingEventStartsInDays[one]=明天
|
||||
upcomingEventStartsInDays[none]=今天
|
||||
sharingMailTooltip=通过邮件分享
|
||||
sharingTwitterTooltip=在 Twitter 上分享
|
||||
sharingFacebookTooltip=在 Facebook 上分享
|
||||
@@ -1808,10 +1808,10 @@ settingsRemoved=默认设置已恢复
|
||||
settingsRemovedError=无法恢复默认设置
|
||||
userSettingsFilter=设置筛选器
|
||||
requiresRegattaRaceAndLeaderboard=此页面需要有效的比赛名称、比赛轮次名称和积分榜名称。
|
||||
couldNotFindRaceInRegatte=无法为名称为{1}的比赛获取名称为{0}的比赛轮次
|
||||
couldNotFindRaceInRegatta=无法为名称为{1}的比赛获取名称为{0}的比赛轮次
|
||||
algorithmTimeOutText=越界
|
||||
mixedLegText=方向不定的风
|
||||
pathCanvasOverlayTitle={0}点的路径。{2}为{1} nmi
|
||||
pathCanvasOverlayTitle={0}点的路径。{2}为{1}NM
|
||||
windSpeedInKnots=风速(节)
|
||||
activeFilters=有效的筛选器:{0}
|
||||
errorLoadingRegattasForLeaderboardGroup=尝试为积分榜组{0}加载相应的比赛出错:{1}
|
||||
@@ -1851,3 +1851,5 @@ errorStartingTracking=开始跟踪{0}失败:{1}
|
||||
errorLoadingLeaderBoardByEvent=尝试加载积分榜出错。活动未与比赛关联或没有跟踪的比赛轮次。
|
||||
errorLoadingEvent=尝试加载编号为{0}的活动出错:{1}
|
||||
errorLoadingRegattaStructure=尝试为编号为{0}的活动加载比赛出错:{1}
|
||||
showEstimatedDuration=显示预计持续时间
|
||||
estimatedDuration=预计持续时间:
|
||||
|
||||
+2
@@ -44,6 +44,8 @@ public class ScoringSchemeTypeFormatter {
|
||||
return stringMessages.scoringSchemeLowPointForLeagueOverallLeaderboard();
|
||||
case LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY:
|
||||
return stringMessages.scoringSchemeLowPointTieBreakBasedOnLastSeriesOnly();
|
||||
case LOW_POINT_WITH_AUTOMATIC_RDG:
|
||||
return stringMessages.scoringSchemeLowPointWithAutomaticRdg();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+16
-11
@@ -5865,18 +5865,23 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
@Override
|
||||
public List<TrackFileImportDeviceIdentifierDTO> getTrackFileImportDeviceIds(List<String> uuids)
|
||||
throws NoCorrespondingServiceRegisteredException, TransformationException {
|
||||
List<TrackFileImportDeviceIdentifierDTO> result = new ArrayList<>();
|
||||
for (String uuidAsString : uuids) {
|
||||
UUID uuid = UUID.fromString(uuidAsString);
|
||||
TrackFileImportDeviceIdentifier device = TrackFileImportDeviceIdentifierImpl.getOrCreate(uuid);
|
||||
long numFixes = getService().getSensorFixStore().getNumberOfFixes(device);
|
||||
TimeRange timeRange = getService().getSensorFixStore().getTimeRangeCoveredByFixes(device);
|
||||
Date from = timeRange == null ? null : timeRange.from().asDate();
|
||||
Date to = timeRange == null ? null : timeRange.to().asDate();
|
||||
result.add(new TrackFileImportDeviceIdentifierDTO(uuidAsString, device.getFileName(), device.getTrackName(),
|
||||
numFixes, from, to));
|
||||
try {
|
||||
final List<TrackFileImportDeviceIdentifierDTO> result = new ArrayList<>();
|
||||
for (String uuidAsString : uuids) {
|
||||
UUID uuid = UUID.fromString(uuidAsString);
|
||||
TrackFileImportDeviceIdentifier device = TrackFileImportDeviceIdentifierImpl.getOrCreate(uuid);
|
||||
long numFixes = getService().getSensorFixStore().getNumberOfFixes(device);
|
||||
TimeRange timeRange = getService().getSensorFixStore().getTimeRangeCoveredByFixes(device);
|
||||
Date from = timeRange == null ? null : timeRange.from().asDate();
|
||||
Date to = timeRange == null ? null : timeRange.to().asDate();
|
||||
result.add(new TrackFileImportDeviceIdentifierDTO(uuidAsString, device.getFileName(), device.getTrackName(),
|
||||
numFixes, from, to));
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception trying to obtain track file import device IDs", e);
|
||||
throw e;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
+2
-1
@@ -23,7 +23,8 @@ public abstract class AbstractConnectivityParamsLoadAndStoreTest extends Abstrac
|
||||
@Before
|
||||
public void setUp() {
|
||||
serviceFinderFactory = new MockConnectivityParamsServiceFinderFactory();
|
||||
racingEventService = new RacingEventServiceImpl(/* clearPersistentCompetitorStore */ true, serviceFinderFactory, /* restoreTrackedRaces */ false);
|
||||
racingEventService = new RacingEventServiceImpl(/* clearPersistentCompetitorStore */ true, serviceFinderFactory,
|
||||
/* restoreTrackedRaces */ false);
|
||||
serviceFinderFactory.setRacingEventService(racingEventService);
|
||||
mongoObjectFactory = racingEventService.getMongoObjectFactory();
|
||||
domainObjectFactory = racingEventService.getDomainObjectFactory();
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+25
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.nmeaconnector.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
@@ -48,4 +49,28 @@ public class NmeaFromZipImportTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testYetAnotherZipFile() throws IOException, InterruptedException {
|
||||
zipInputStream = new ZipInputStream(new FileInputStream("resources/NMEA.zip"));
|
||||
ZipEntry entry;
|
||||
while ((entry=zipInputStream.getNextEntry()) != null) {
|
||||
if (entry.getName().toLowerCase().endsWith(".txt")) {
|
||||
Iterable<Wind> windFixes = NmeaFactory.INSTANCE.readWind(zipInputStream);
|
||||
assertEquals(740, Util.size(windFixes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZipFileExcerptWithOnlyEmptyWindRecords() throws IOException, InterruptedException {
|
||||
zipInputStream = new ZipInputStream(new FileInputStream("resources/NMEA_Snippet.zip"));
|
||||
ZipEntry entry;
|
||||
while ((entry=zipInputStream.getNextEntry()) != null) {
|
||||
if (entry.getName().toLowerCase().endsWith(".txt")) {
|
||||
Iterable<Wind> windFixes = NmeaFactory.INSTANCE.readWind(zipInputStream);
|
||||
assertTrue(Util.isEmpty(windFixes));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
PolarChain1=Polars
|
||||
PolarChain2=Backend Polars
|
||||
RaceColumn=RaceColumn
|
||||
LegIndex=Leg Index
|
||||
PolarData=Polar Data
|
||||
Polars=Polars
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
PolarChain1=Polare
|
||||
PolarChain2=Backend-Polare
|
||||
RaceColumn=Rennen
|
||||
LegIndex=Schenkel Index
|
||||
PolarData=Polardaten
|
||||
Polars=Polare
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
PolarChain1=Polares
|
||||
PolarChain2=Polares back end
|
||||
RaceColumn=Columna carrera
|
||||
LegIndex=Índice de tramo
|
||||
PolarData=Datos polares
|
||||
Polars=Polares
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
PolarChain1=Polaires
|
||||
PolarChain2=Polaires backend
|
||||
RaceColumn=Colonne de course
|
||||
LegIndex=Index des portions de parcours
|
||||
PolarData=Données polaires
|
||||
Polars=Polaires
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
PolarChain1=ポーラー
|
||||
PolarChain2=バックエンドポーラー
|
||||
RaceColumn=レース列
|
||||
LegIndex=レグ指数
|
||||
PolarData=ポーラーデータ
|
||||
Polars=ポーラー
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
PolarChain1=Cartas polares
|
||||
PolarChain2=Cartas polares back-end
|
||||
RaceColumn=Coluna de corrida
|
||||
LegIndex=Índice de pernas
|
||||
PolarData=Dados da carta polar
|
||||
Polars=Cartas polares
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
PolarChain1=Полярные данные
|
||||
PolarChain2=Полярные данные бэкэнда
|
||||
RaceColumn=Столбец гонки
|
||||
LegIndex=Индекс отрезка
|
||||
PolarData=Полярные данные
|
||||
Polars=Полярные данные
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
PolarChain1=极坐标图
|
||||
PolarChain2=后端极坐标图
|
||||
RaceColumn=比赛轮次列
|
||||
LegIndex=航段索引
|
||||
PolarData=极坐标数据
|
||||
Polars=极坐标图
|
||||
|
||||
+6
-2
@@ -33,6 +33,7 @@ import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
|
||||
import com.sap.sailing.domain.common.Wind;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
@@ -46,6 +47,7 @@ import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
|
||||
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
|
||||
import com.sap.sailing.domain.tracking.WindWithConfidence;
|
||||
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl;
|
||||
@@ -82,7 +84,7 @@ public class PolarDataMinerTest {
|
||||
return new BearingClusterGroup(0, 180, 5);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Ignore("The test did work before DataMining was used for polars; maybe rework in the future...")
|
||||
@Test
|
||||
public void testGrouping() throws InterruptedException, TimeoutException, NoSuchMethodException,
|
||||
NotEnoughDataHasBeenAddedException {
|
||||
@@ -203,7 +205,9 @@ public class PolarDataMinerTest {
|
||||
MarkPassing markpassing = createMockedStartMarkPassing();
|
||||
when(trackedRace.getMarkPassing(eq(competitor), eq(startWaypoint))).thenReturn(markpassing);
|
||||
}
|
||||
|
||||
TrackedRaceStatus status = mock(TrackedRaceStatus.class);
|
||||
when(status.getStatus()).thenReturn(TrackedRaceStatusEnum.FINISHED);
|
||||
when(trackedRace.getStatus()).thenReturn(status);
|
||||
|
||||
|
||||
when(trackedRace.getStartOfRace()).thenReturn(startOfRace);
|
||||
|
||||
-1
@@ -25,7 +25,6 @@ public abstract class AbstractEnrichingProcessor<InputType, ResultType> extends
|
||||
@Override
|
||||
protected ProcessorInstruction<ResultType> createInstruction(final InputType element) {
|
||||
return new AbstractProcessorInstruction<ResultType>(this) {
|
||||
|
||||
@Override
|
||||
public ResultType computeResult() {
|
||||
return enrich(element);
|
||||
|
||||
@@ -13,9 +13,10 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
|
||||
@@ -42,6 +43,7 @@ import com.sap.sailing.domain.common.impl.PolarSheetsHistogramDataImpl;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
|
||||
import com.sap.sailing.domain.polars.PolarsChangedListener;
|
||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.impl.CubicEquation;
|
||||
import com.sap.sse.datamining.components.FilterCriterion;
|
||||
@@ -73,7 +75,10 @@ public class PolarDataMiner {
|
||||
private static final int EXECUTOR_QUEUE_SIZE = 100;
|
||||
private static final int THREAD_POOL_SIZE = ThreadPoolUtil.INSTANCE.getReasonableThreadPoolSize();
|
||||
private final ThreadPoolExecutor executor = createExecutor();
|
||||
private final ConcurrentMap<TrackedRace, Set<GPSFixMovingWithOriginInfo>> fixesForRacesWhichAreStillLoading = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService processRacesThatFinishedLoadingExecutor = ThreadPoolUtil.INSTANCE
|
||||
.createBackgroundTaskThreadPoolExecutor(1,
|
||||
PolarDataMiner.class.getName() + " processRacesThatFinishedLoadingExecutor");
|
||||
private final Map<BoatClass, AtomicInteger> stats = new ConcurrentHashMap<>();
|
||||
|
||||
private final Queue<GPSFixMovingWithOriginInfo> fixQueue = new ConcurrentLinkedQueue<GPSFixMovingWithOriginInfo>();
|
||||
|
||||
@@ -97,21 +102,13 @@ public class PolarDataMiner {
|
||||
private ThreadPoolExecutor createExecutor() {
|
||||
return new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE, 60l, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(EXECUTOR_QUEUE_SIZE), new ThreadFactoryWithPriority(PolarDataMiner.class.getSimpleName(),
|
||||
Thread.NORM_PRIORITY-1, /* daemon */true), new RejectedExecutionHandler() {
|
||||
@Override
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
|
||||
logger.warning("Polar Data Miner Executor rejected execution. Running sequentially.");
|
||||
r.run();
|
||||
}
|
||||
}) {
|
||||
Thread.NORM_PRIORITY-1, /* daemon */true)) {
|
||||
@Override
|
||||
protected void afterExecute(Runnable r, Throwable t) {
|
||||
super.afterExecute(r, t);
|
||||
synchronized (fixQueue) {
|
||||
while (this.getQueue().size() < (EXECUTOR_QUEUE_SIZE / 10) && !fixQueue.isEmpty()) {
|
||||
GPSFixMovingWithOriginInfo fix = fixQueue.poll();
|
||||
preFilteringProcessor.processElement(fix);
|
||||
}
|
||||
GPSFixMovingWithOriginInfo fix;
|
||||
while (this.getQueue().size() < (EXECUTOR_QUEUE_SIZE / 10) && (fix=fixQueue.poll()) != null) {
|
||||
preFilteringProcessor.processElement(fix);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -193,7 +190,6 @@ public class PolarDataMiner {
|
||||
preFilteringProcessor = new ParallelFilteringProcessor<GPSFixMovingWithOriginInfo>(
|
||||
GPSFixMovingWithOriginInfo.class, executor, preFilterResultReceivers,
|
||||
new FilterCriterion<GPSFixMovingWithOriginInfo>() {
|
||||
|
||||
@Override
|
||||
public boolean matches(GPSFixMovingWithOriginInfo element) {
|
||||
boolean result = false;
|
||||
@@ -201,6 +197,14 @@ public class PolarDataMiner {
|
||||
element.getCompetitor(),
|
||||
backendPolarSheetGenerationSettings.getPctOfLeadingCompetitorsToInclude())) {
|
||||
result = true;
|
||||
final BoatClass boatClass = element.getCompetitor().getBoat().getBoatClass();
|
||||
AtomicInteger count = stats.get(boatClass);
|
||||
if (count == null) {
|
||||
count = new AtomicInteger(1);
|
||||
stats.put(boatClass, count);
|
||||
} else {
|
||||
count.getAndIncrement();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -214,36 +218,20 @@ public class PolarDataMiner {
|
||||
}
|
||||
|
||||
public void addFix(GPSFixMoving fix, Competitor competitor, TrackedRace trackedRace) {
|
||||
GPSFixMovingWithOriginInfo fixWithOriginInfo = new GPSFixMovingWithOriginInfo(fix, trackedRace, competitor);
|
||||
if (trackedRace.getStatus().getStatus() == TrackedRaceStatusEnum.LOADING) {
|
||||
/*
|
||||
* logger.info("Received fix for replay race which has not finished loading. Queuing. " +
|
||||
* (trackedRace.getRace() != null ? trackedRace.getRace().getName() : trackedRace.getRaceIdentifier()
|
||||
* .getRaceName()));
|
||||
*/
|
||||
Set<GPSFixMovingWithOriginInfo> fixes = fixesForRacesWhichAreStillLoading.get(trackedRace);
|
||||
if (fixes == null) {
|
||||
synchronized (fixesForRacesWhichAreStillLoading) {
|
||||
fixes = fixesForRacesWhichAreStillLoading.get(trackedRace);
|
||||
if (fixes == null) {
|
||||
fixes = new HashSet<>();
|
||||
fixesForRacesWhichAreStillLoading.put(trackedRace, fixes);
|
||||
}
|
||||
}
|
||||
}
|
||||
fixes.add(fixWithOriginInfo);
|
||||
} else {
|
||||
// don't process fixes while LOADING because wind data is loading at the same time, and
|
||||
// unpredictable results may occur due to this
|
||||
if (trackedRace.getStatus().getStatus() != TrackedRaceStatusEnum.LOADING) {
|
||||
GPSFixMovingWithOriginInfo fixWithOriginInfo = new GPSFixMovingWithOriginInfo(fix, trackedRace, competitor);
|
||||
processFix(trackedRace, fixWithOriginInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void processFix(TrackedRace trackedRace, GPSFixMovingWithOriginInfo fixWithOriginInfo) {
|
||||
synchronized (fixQueue) {
|
||||
if (executor.getQueue().size() >= EXECUTOR_QUEUE_SIZE / 10) {
|
||||
fixQueue.add(fixWithOriginInfo);
|
||||
} else {
|
||||
preFilteringProcessor.processElement(fixWithOriginInfo);
|
||||
}
|
||||
if (executor.getQueue().size() >= EXECUTOR_QUEUE_SIZE / 10) { // in this case synchronous execution becomes likely, but
|
||||
// we are in a synchronous callback and don't want to spend too much time here in this foreground thread; queue it!
|
||||
fixQueue.add(fixWithOriginInfo);
|
||||
} else {
|
||||
preFilteringProcessor.processElement(fixWithOriginInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,18 +421,25 @@ public class PolarDataMiner {
|
||||
return speedRegressionPerAngleClusterProcessor.getSpeedRegressionFunction(boatClass, trueWindAngle);
|
||||
}
|
||||
|
||||
public void raceFinishedTracking(TrackedRace race) {
|
||||
Set<GPSFixMovingWithOriginInfo> fixes = null;
|
||||
synchronized (fixesForRacesWhichAreStillLoading) {
|
||||
fixes = fixesForRacesWhichAreStillLoading.remove(race);
|
||||
}
|
||||
if (fixes != null) {
|
||||
public void raceFinishedTracking(final TrackedRace race) {
|
||||
processRacesThatFinishedLoadingExecutor.execute(()->{
|
||||
logger.info("All queued fixes for newly loaded race will process now. "
|
||||
+ (race.getRace() != null ? race.getRace().getName() : race.getRaceIdentifier().getRaceName()));
|
||||
for (GPSFixMovingWithOriginInfo fix : fixes) {
|
||||
processFix(race, fix);
|
||||
for (final Competitor competitor : race.getRace().getCompetitors()) {
|
||||
final GPSFixTrack<Competitor, GPSFixMoving> track = race.getTrack(competitor);
|
||||
track.lockForRead();
|
||||
try {
|
||||
for (final GPSFixMoving fix : track.getFixes()) {
|
||||
preFilteringProcessor.processElement(new GPSFixMovingWithOriginInfo(fix, race, competitor));
|
||||
}
|
||||
} finally {
|
||||
track.unlockAfterRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info("Finished injecting fixes for race "
|
||||
+ (race.getRace() != null ? race.getRace().getName() : race.getRaceIdentifier().getRaceName())
|
||||
+ "; stats: " + stats);
|
||||
});
|
||||
}
|
||||
|
||||
public void registerListener(BoatClass boatClass, PolarsChangedListener listener) {
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.sap.sailing.server.gateway.serialization.impl;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.sap.sailing.domain.anniversary.DetailedRaceInfo;
|
||||
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
||||
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
|
||||
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
public class DetailedRaceInfoJsonSerializer
|
||||
implements JsonSerializer<DetailedRaceInfo>, JsonDeserializer<DetailedRaceInfo> {
|
||||
|
||||
public static final String FIELD_EVENT_ID = "eventID";
|
||||
public static final String FIELD_LEADERBOARD_NAME = "leaderboardName";
|
||||
public static final String FIELD_REMOTEURL = "remoteUrl";
|
||||
public static final String FIELD_RACES = "races";
|
||||
|
||||
@Override
|
||||
public JSONObject serialize(DetailedRaceInfo object) {
|
||||
JSONObject result = new JSONObject();
|
||||
result.put(FIELD_EVENT_ID, object.getEventID().toString());
|
||||
result.put(SimpleRaceInfoJsonSerializer.FIELD_RACE_NAME, object.getIdentifier().getRaceName());
|
||||
result.put(SimpleRaceInfoJsonSerializer.FIELD_REGATTA_NAME, object.getIdentifier().getRegattaName());
|
||||
result.put(FIELD_LEADERBOARD_NAME, object.getLeaderboardName());
|
||||
result.put(SimpleRaceInfoJsonSerializer.FIELD_START_OF_RACE, object.getStartOfRace().asMillis());
|
||||
final URL remoteUrl = object.getRemoteUrl();
|
||||
result.put(FIELD_REMOTEURL, remoteUrl == null ? null : remoteUrl.toExternalForm());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DetailedRaceInfo deserialize(JSONObject object) throws JsonDeserializationException {
|
||||
String eventId = object.get(FIELD_EVENT_ID).toString();
|
||||
String raceName = object.get(SimpleRaceInfoJsonSerializer.FIELD_RACE_NAME).toString();
|
||||
String regattaName = object.get(SimpleRaceInfoJsonSerializer.FIELD_REGATTA_NAME).toString();
|
||||
String leaderboardName = object.get(FIELD_LEADERBOARD_NAME).toString();
|
||||
TimePoint startOfRace = new MillisecondsTimePoint(
|
||||
((Number) object.get(SimpleRaceInfoJsonSerializer.FIELD_START_OF_RACE)).longValue());
|
||||
String remoteUrl = (String) object.get(FIELD_REMOTEURL);
|
||||
URL remoteUrlObj = null;
|
||||
if (remoteUrl != null && !remoteUrl.isEmpty()) {
|
||||
try {
|
||||
remoteUrlObj = new URL(remoteUrl);
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return new DetailedRaceInfo(new RegattaNameAndRaceName(regattaName, raceName), leaderboardName, startOfRace,
|
||||
UUID.fromString(eventId), remoteUrlObj);
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.sap.sailing.server.gateway.serialization.impl;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.sap.sailing.domain.anniversary.SimpleRaceInfo;
|
||||
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
||||
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
|
||||
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
public class SimpleRaceInfoJsonSerializer implements JsonSerializer<SimpleRaceInfo> {
|
||||
|
||||
public static final String FIELD_RACE_NAME = "raceName";
|
||||
public static final String FIELD_REGATTA_NAME = "regattaName";
|
||||
public static final String FIELD_START_OF_RACE = "startOfRaceAsMillis";
|
||||
|
||||
@Override
|
||||
public JSONObject serialize(SimpleRaceInfo object) {
|
||||
JSONObject result = new JSONObject();
|
||||
result.put(FIELD_RACE_NAME, object.getIdentifier().getRaceName());
|
||||
result.put(FIELD_REGATTA_NAME, object.getIdentifier().getRegattaName());
|
||||
result.put(FIELD_START_OF_RACE, object.getStartOfRace().asMillis());
|
||||
return result;
|
||||
}
|
||||
|
||||
public SimpleRaceInfo deserialize(JSONObject object,URL remoteUrl) throws JsonDeserializationException {
|
||||
String raceName = object.get(SimpleRaceInfoJsonSerializer.FIELD_RACE_NAME).toString();
|
||||
String regattaName = object.get(SimpleRaceInfoJsonSerializer.FIELD_REGATTA_NAME).toString();
|
||||
TimePoint startOfRace = new MillisecondsTimePoint(((Number)object.get(SimpleRaceInfoJsonSerializer.FIELD_START_OF_RACE)).longValue());
|
||||
return new SimpleRaceInfo(new RegattaNameAndRaceName(regattaName, raceName), startOfRace, remoteUrl);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -40,7 +40,8 @@ public abstract class AbstractJaxRsApiTest {
|
||||
public void setUp() {
|
||||
service = MongoDBConfiguration.getDefaultTestConfiguration().getService();
|
||||
service.getDB().dropDatabase();
|
||||
racingEventService = new RacingEventServiceImpl(/* clearPersistentCompetitorStore */ true, new MockSmartphoneUuidServiceFinderFactory(), /* restoreTrackedRaces */ false);
|
||||
racingEventService = new RacingEventServiceImpl(/* clearPersistentCompetitorStore */ true,
|
||||
new MockSmartphoneUuidServiceFinderFactory(), /* restoreTrackedRaces */ false);
|
||||
}
|
||||
|
||||
protected <T extends AbstractSailingServerResource> T spyResource(T resource) {
|
||||
|
||||
+2
-1
@@ -45,7 +45,8 @@ public class GPSFixesResourceTest {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
service = new RacingEventServiceImpl(/* clearPersistentCompetitorStore */ true, new MockSmartphoneUuidServiceFinderFactory(), /* restoreTrackedRaces */ false);
|
||||
service = new RacingEventServiceImpl(/* clearPersistentCompetitorStore */ true,
|
||||
new MockSmartphoneUuidServiceFinderFactory(), /* restoreTrackedRaces */ false);
|
||||
service.getMongoObjectFactory().getDatabase().dropDatabase();
|
||||
}
|
||||
|
||||
|
||||
+17
-9
@@ -1,6 +1,8 @@
|
||||
package com.sap.sailing.server.gateway.impl.rc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
@@ -11,11 +13,13 @@ import org.json.simple.JSONArray;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.race.RaceLog;
|
||||
import com.sap.sailing.domain.base.CourseArea;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.racegroup.RaceGroup;
|
||||
import com.sap.sailing.domain.common.racelog.RaceLogServletConstants;
|
||||
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
|
||||
import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
|
||||
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.BoatClassJsonSerializer;
|
||||
@@ -41,7 +45,6 @@ public class RaceGroupJsonExportServlet extends AbstractJsonHttpServlet {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
||||
|
||||
String courseAreaFilter = request.getParameter(RaceLogServletConstants.PARAM_COURSE_AREA_FILTER);
|
||||
if (courseAreaFilter == null) {
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Need to set a course area filter.");
|
||||
@@ -52,7 +55,6 @@ public class RaceGroupJsonExportServlet extends AbstractJsonHttpServlet {
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Course area filter must be valid UUID.");
|
||||
return;
|
||||
}
|
||||
|
||||
CourseArea filterCourseArea = getService().getCourseArea(courseAreaId);
|
||||
if (filterCourseArea == null) {
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND, "No course area found with given UUID.");
|
||||
@@ -67,20 +69,26 @@ public class RaceGroupJsonExportServlet extends AbstractJsonHttpServlet {
|
||||
}
|
||||
JsonSerializer<RaceGroup> serializer = createSerializer(clientUuid);
|
||||
JSONArray result = new JSONArray();
|
||||
|
||||
RaceGroupFactory raceGroupFactory = new RaceGroupFactory();
|
||||
final Set<Regatta> regattasForWhichRegattaLeaderboardsWereAdded = new HashSet<>();
|
||||
for (Leaderboard leaderboard : getService().getLeaderboards().values()) {
|
||||
if (filterCourseArea.equals(leaderboard.getDefaultCourseArea())) {
|
||||
RaceGroup raceGroup = null;
|
||||
if (leaderboard instanceof RegattaLeaderboard) {
|
||||
raceGroup = raceGroupFactory.convert((RegattaLeaderboard) leaderboard);
|
||||
if (leaderboard instanceof RegattaLeaderboard && !(leaderboard instanceof RegattaLeaderboardWithEliminations)) {
|
||||
result.add(serializer.serialize(raceGroupFactory.convert((RegattaLeaderboard) leaderboard)));
|
||||
regattasForWhichRegattaLeaderboardsWereAdded.add(((RegattaLeaderboard) leaderboard).getRegatta());
|
||||
} else if (leaderboard instanceof FlexibleLeaderboard) {
|
||||
raceGroup = raceGroupFactory.convert((FlexibleLeaderboard) leaderboard);
|
||||
result.add(serializer.serialize(raceGroupFactory.convert((FlexibleLeaderboard) leaderboard)));
|
||||
}
|
||||
result.add(serializer.serialize(raceGroup));
|
||||
}
|
||||
}
|
||||
|
||||
// now add only those RegattaLeaderboardWithEliminations for which the original RegattaLeaderboard hasn't been added
|
||||
for (Leaderboard leaderboard : getService().getLeaderboards().values()) {
|
||||
if (leaderboard instanceof RegattaLeaderboardWithEliminations &&
|
||||
filterCourseArea.equals(leaderboard.getDefaultCourseArea()) &&
|
||||
!regattasForWhichRegattaLeaderboardsWereAdded.contains(((RegattaLeaderboardWithEliminations) leaderboard).getRegatta())) {
|
||||
result.add(serializer.serialize(raceGroupFactory.convert((RegattaLeaderboardWithEliminations) leaderboard)));
|
||||
}
|
||||
}
|
||||
setJsonResponseHeader(response);
|
||||
result.writeJSONString(response.getWriter());
|
||||
}
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ public class RestApiApplication extends Application {
|
||||
classes.add(FileStorageResource.class);
|
||||
classes.add(DataMiningResource.class);
|
||||
classes.add(PingResource.class);
|
||||
classes.add(TrackedRaceListResource.class);
|
||||
classes.add(StatisticsResource.class);
|
||||
|
||||
// Exception Mapper
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package com.sap.sailing.server.gateway.jaxrs.api;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONAware;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import com.sap.sailing.domain.anniversary.DetailedRaceInfo;
|
||||
import com.sap.sailing.domain.anniversary.SimpleRaceInfo;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
||||
import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.DetailedRaceInfoJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.SimpleRaceInfoJsonSerializer;
|
||||
|
||||
@Path("/v1/trackedRaces")
|
||||
public class TrackedRaceListResource extends AbstractSailingServerResource {
|
||||
private static final String HEADER_NAME_CONTENT_TYPE = "Content-Type";
|
||||
private static final String CONTENT_TYPE_JSON_UTF8 = MediaType.APPLICATION_JSON + ";charset=UTF-8";
|
||||
|
||||
private final SimpleRaceInfoJsonSerializer simpleRaceListJsonSerializer = new SimpleRaceInfoJsonSerializer();
|
||||
private final DetailedRaceInfoJsonSerializer detailedRaceListJsonSerializer = new DetailedRaceInfoJsonSerializer();
|
||||
|
||||
/**
|
||||
* Allows to query for more details on a specific race, implemented to allow for example to retrieve more
|
||||
* information about an anniversary. This call works transitively by asking a server that is known to have the race
|
||||
* in question in case that the race isn't found locally.
|
||||
*/
|
||||
@GET
|
||||
@Produces(CONTENT_TYPE_JSON_UTF8)
|
||||
@Path("raceDetails")
|
||||
public Response getDetailsForRace(@QueryParam("raceName") String raceName,
|
||||
@QueryParam("regattaName") String regattaName) {
|
||||
final DetailedRaceInfo detailedRaceInfo = getService().getFullDetailsForRaceLocal(new RegattaNameAndRaceName(regattaName, raceName));
|
||||
if (detailedRaceInfo == null) {
|
||||
return Response.status(Status.NOT_FOUND).header(HEADER_NAME_CONTENT_TYPE, CONTENT_TYPE_JSON_UTF8).build();
|
||||
}
|
||||
return getJsonResponse(detailedRaceListJsonSerializer.serialize(detailedRaceInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of tracked races. By default, only TrackedRaces from the local instance are returned. The entries
|
||||
* are grouped by the remote URL from where they originated. Local entries have a {@code null} value for the
|
||||
* {@link DetailedRaceInfoJsonSerializer# FIELD_REMOTEURL remote URL} field. The order of the list returned is
|
||||
* undefined.<br>
|
||||
*
|
||||
* TODO bug 4227: implement a parameter to optionally add remote {@link TrackedRace TrackedRaces} to the list and
|
||||
* extend this JavaDoc accordingly.
|
||||
*/
|
||||
@GET
|
||||
@Produces(CONTENT_TYPE_JSON_UTF8)
|
||||
@Path("getRaces")
|
||||
public Response raceList(@QueryParam("transitive") Boolean transitive) {
|
||||
HashMap<URL, List<SimpleRaceInfo>> raceData = new HashMap<>();
|
||||
raceData.put(null, new ArrayList<>(getService().getLocalRaceList().values()));
|
||||
if (transitive != null && Boolean.TRUE.equals(transitive)) {
|
||||
for (SimpleRaceInfo remoteRace : getService().getRemoteRaceList().values()) {
|
||||
URL remoteUrl = remoteRace.getRemoteUrl();
|
||||
List<SimpleRaceInfo> remoteList = raceData.get(remoteUrl);
|
||||
if (remoteList == null) {
|
||||
remoteList = new ArrayList<>();
|
||||
raceData.put(remoteUrl, remoteList);
|
||||
}
|
||||
remoteList.add(remoteRace);
|
||||
}
|
||||
}
|
||||
JSONArray json = new JSONArray();
|
||||
for (Entry<URL, List<SimpleRaceInfo>> raced : raceData.entrySet()) {
|
||||
JSONArray list = new JSONArray();
|
||||
for (SimpleRaceInfo simpleRaceInfo : raced.getValue()) {
|
||||
list.add(simpleRaceListJsonSerializer.serialize(simpleRaceInfo));
|
||||
}
|
||||
JSONObject remote = new JSONObject();
|
||||
final URL remoteURL = raced.getKey();
|
||||
remote.put(DetailedRaceInfoJsonSerializer.FIELD_REMOTEURL, remoteURL == null ? null : remoteURL.toExternalForm());
|
||||
remote.put(DetailedRaceInfoJsonSerializer.FIELD_RACES, list);
|
||||
json.add(remote);
|
||||
}
|
||||
return getJsonResponse(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all locally and remote tracked races that are currently known. The list is sorted by the
|
||||
* {@link SimpleRaceInfoJsonSerializer#FIELD_START_OF_RACE} field, and each {@link SimpleRaceInfo} object is put
|
||||
* together with an incrementing number starting at 0. Duplicate races are eliminated such that local copies take
|
||||
* precedence over remote ones. Races hosted on the server on which this method is invoked will be grouped under the
|
||||
* {@code null} value for the {@code remoteUrl} field.
|
||||
*/
|
||||
@GET
|
||||
@Produces(CONTENT_TYPE_JSON_UTF8)
|
||||
@Path("allRaces")
|
||||
public Response fullRaceList() {
|
||||
JSONArray json = new JSONArray();
|
||||
Map<RegattaAndRaceIdentifier, SimpleRaceInfo> store = new HashMap<>();
|
||||
store.putAll(getService().getRemoteRaceList());
|
||||
store.putAll(getService().getLocalRaceList());
|
||||
ArrayList<SimpleRaceInfo> sorted = new ArrayList<>(store.values());
|
||||
Collections.sort(sorted, new Comparator<SimpleRaceInfo>() {
|
||||
@Override
|
||||
public int compare(SimpleRaceInfo o1, SimpleRaceInfo o2) {
|
||||
return o1.getStartOfRace().compareTo(o2.getStartOfRace());
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < sorted.size(); i++) {
|
||||
SimpleRaceInfo current = sorted.get(i);
|
||||
JSONObject raceInfo = new JSONObject();
|
||||
raceInfo.put("racenumber", String.valueOf(i));
|
||||
final URL remoteUrl = current.getRemoteUrl();
|
||||
raceInfo.put("remoteUrl", remoteUrl == null ? null : remoteUrl.toExternalForm());
|
||||
raceInfo.put("raceinfo", simpleRaceListJsonSerializer.serialize(current));
|
||||
json.add(raceInfo);
|
||||
}
|
||||
return getJsonResponse(json);
|
||||
}
|
||||
|
||||
private Response getJsonResponse(JSONAware json) {
|
||||
return Response.ok(json.toJSONString()).header(HEADER_NAME_CONTENT_TYPE, CONTENT_TYPE_JSON_UTF8).build();
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -7,6 +7,8 @@ import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -34,7 +36,7 @@ import com.sap.sailing.server.gateway.windimport.AbstractWindImportServlet.WindI
|
||||
import com.sap.sse.common.Util;
|
||||
|
||||
public abstract class AbstractWindImportServlet extends SailingServerHttpServlet {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AbstractWindImportServlet.class.getName());
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static class UploadRequest {
|
||||
@@ -192,6 +194,7 @@ public abstract class AbstractWindImportServlet extends SailingServerHttpServlet
|
||||
// Use text/html to prevent browsers from wrapping the response body,
|
||||
// see "Handling File Upload Responses in GWT" at http://www.artofsolving.com/node/50
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error ocurred trying to import wind fixes", e);
|
||||
windImportResult.error = e.toString();
|
||||
}
|
||||
response.setContentType("text/html;charset=UTF-8");
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<link href='../../../../sailing-fontface-1.0.cache.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
|
||||
<link href='../../../main.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
|
||||
<link rel='icon' href='../../../../sap.ico' type='image/x-icon'>
|
||||
<title>SAP Sailing Analytics Webservices API Version 1.0</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>SAP Sailing Analytics Webservices API Version 1.0</h1>
|
||||
<h2>URL: /api/v1/trackedRaces/allRaces</h2>
|
||||
|
||||
<b>Description:</b>
|
||||
<p>Gets all TrackedRaces found on the local instance as well as the
|
||||
collected TrackedRaces from all remote instances. Only those
|
||||
TrackedRaces are contained that are reachable by any Event and have a
|
||||
valid startOfRace. The list consolidated to not contain duplicates.
|
||||
The list is sorted and numbered by startOfRace. Take note that this
|
||||
list can contain past and future races and starts counting at zero.
|
||||
This call is only a temporary solution, and will be removed at a later
|
||||
point in time, use getRaces & raceDetails instead.</p>
|
||||
<br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>Webservice Type:</td>
|
||||
<td>REST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Output format:</td>
|
||||
<td>Json</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mandatory parameters:</td>
|
||||
<td>None</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Optional parameters:</td>
|
||||
<td>None</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Request method:</td>
|
||||
<td>GET</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example:</td>
|
||||
<td><a href="http://www.sapsailing.com/sailingserver/api/v1/trackedRaces/allRaces">http://www.sapsailing.com/sailingserver/api/v1/trackedRaces/allRaces</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="height: 1em;"></div>
|
||||
<a href="index.html">Back to Web Service Overview</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<link href='../../../../sailing-fontface-1.0.cache.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
|
||||
<link href='../../../main.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
|
||||
<link rel='icon' href='../../../../sap.ico' type='image/x-icon'>
|
||||
<title>SAP Sailing Analytics Webservices API Version 1.0</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>SAP Sailing Analytics Webservices API Version 1.0</h1>
|
||||
<h2>URL: /api/v1/trackedRaces/getRaces</h2>
|
||||
|
||||
<b>Description:</b>
|
||||
<p>Gets all TrackedRaces found on the local instance, and optionally (depending on the "transitive" parameter) its remote servers.
|
||||
Only those TrackedRaces are contained that are reachable by any Event and have a valid startOfRace.</p>
|
||||
<br/>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Webservice Type:</td>
|
||||
<td>REST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Output format:</td>
|
||||
<td>Json</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mandatory parameters:</td>
|
||||
<td>None</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Optional parameters:</td>
|
||||
<td>
|
||||
<div>transitive (true or false) - determines whether the races from other servers referenced remotely shall be collected in the response</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Request method:</td>
|
||||
<td>GET</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example:</td>
|
||||
<td><a href="http://www.sapsailing.com/sailingserver/api/v1/trackedRaces/getRaces">http://www.sapsailing.com/sailingserver/api/v1/trackedRaces/getRaces</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="height: 1em;"></div>
|
||||
<a href="index.html">Back to Web Service Overview</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -178,9 +178,24 @@ name to make sure you get to the event you're interested in.
|
||||
<td>Gets all maneuvers performed by all competitors of a race</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="statisticsGetDoc.html">/v1/statistics/years?[years={year}[&years={year}]]</a></td>
|
||||
<td><a href="statisticsGetDoc.html">/api/v1/statistics/years?[years={year}[&years={year}]]</a></td>
|
||||
<td>Gets statistics regarding regattas and races grouped by years</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="allRacesGetDoc.html">/api/v1/trackedRaces/allRaces</a></td>
|
||||
<td>Gets all TrackedRaces found on the local instance as well as the collected TrackedRaces from all remote instances
|
||||
and provides them in a list sorted by race start time, together with a sequence number for each one. This is a
|
||||
simple way to check how many races have been tracked altogether and which one may be an "anniversary race."
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="getRacesGetDoc.html">/api/v1/trackedRaces/getRaces</a></td>
|
||||
<td>Gets all TrackedRaces found on the local instance with a valid start time.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="raceDetailsGetDoc.html">/api/v1/trackedRaces/raceDetails?raceName={raceName}&regattaName={regattaName}</a></td>
|
||||
<td>Gets more information about a specified RegattaAndRace identifier</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="height: 1em;"></div>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<link href='../../../../sailing-fontface-1.0.cache.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
|
||||
<link href='../../../main.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
|
||||
<link rel='icon' href='../../../../sap.ico' type='image/x-icon'>
|
||||
<title>SAP Sailing Analytics Webservices API Version 1.0</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>SAP Sailing Analytics Webservices API Version 1.0</h1>
|
||||
<h2>URL: /api/v1/trackedRaces/raceDetails</h2>
|
||||
|
||||
<b>Description:</b>
|
||||
<p>Gets all matching races that are stored locally or on remoteservers that match the raceidentifier. This call works transient and can hop multiple servers. It should be expected that multiple results will be returned with different leaderboardNames, it is the responsibility of the caller to filter these based on the usecase.</p>
|
||||
<br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>Webservice Type:</td>
|
||||
<td>REST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Output format:</td>
|
||||
<td>Json</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mandatory parameters:</td>
|
||||
<td>raceName<br>
|
||||
regattaName
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Optional parameters:</td>
|
||||
<td>None</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Request method:</td>
|
||||
<td>GET</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Example:</td>
|
||||
<td><a href="http://www.sapsailing.com/sailingserver/api/v1/trackedRaces/raceDetails?raceName=Race%202®attaName=ESS%202016%20Cardiff">http://www.sapsailing.com/sailingserver/api/v1/trackedRaces/raceDetails?raceName=Race 2&regattaName=ESS 2016 Cardiff</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="height: 1em;"></div>
|
||||
<a href="index.html">Back to Web Service Overview</a>
|
||||
</body>
|
||||
</html>
|
||||
-2
@@ -139,8 +139,6 @@ public class MediaMasterDataExportTest {
|
||||
Fleet defaultFleet = raceColumn.getFleets().iterator().next();
|
||||
raceColumn.setRaceIdentifier(defaultFleet, regattaAndRaceIdentifier);
|
||||
}
|
||||
|
||||
|
||||
List<? extends Leaderboard> leaderboards = Arrays.asList(regattaLeaderboard, flexibleLeaderboard);
|
||||
LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("name", "description", "displayName", displayGroupsInReverseOrder , leaderboards);
|
||||
Set<LeaderboardGroup> groupsToExport = Collections.singleton(leaderboardGroup);
|
||||
|
||||
@@ -19,6 +19,8 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog;
|
||||
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
|
||||
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
|
||||
import com.sap.sailing.domain.anniversary.DetailedRaceInfo;
|
||||
import com.sap.sailing.domain.anniversary.SimpleRaceInfo;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.CompetitorStore;
|
||||
import com.sap.sailing.domain.base.CourseArea;
|
||||
@@ -682,4 +684,53 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch
|
||||
* @return a map of {@link Statistics statistic objects} keyed be the year they are representing
|
||||
*/
|
||||
Map<Integer, Statistics> getOverallStatisticsByYear();
|
||||
|
||||
/**
|
||||
* Obtains information about all {@link TrackedRace}s connected to {@link Event}s managed locally on this server
|
||||
* instance or reachable through a remote server reference, having a non-{@code null}
|
||||
* {@link TrackedRace#getStartOfRace() start time}. Being "connected" here means that the race is linked to a
|
||||
* {@link Leaderboard} that is part of a {@link LeaderboardGroup} which is in turn
|
||||
* {@link Event#getLeaderboardGroups() linked} to the {@link Event}.
|
||||
*
|
||||
* @return a new map whose keys identify the race and whose values have a short info about the race that will allow,
|
||||
* e.g., to sort by start time and therefore identify "anniversary" races in a central instance. All
|
||||
* {@link SimpleRaceInfo#getRemoteUrl()} values will be {@code null} for races managed locally on this server;
|
||||
* for races obtained through remote server references, the remote URL will be that of the remote server
|
||||
* reference. Callers may modify the map as each call to this method will produce a new copy.
|
||||
*/
|
||||
Map<RegattaAndRaceIdentifier, SimpleRaceInfo> getRemoteRaceList();
|
||||
|
||||
/**
|
||||
* Obtains information about all {@link TrackedRace}s connected to {@link Event}s managed locally on this server
|
||||
* instance, having a non-{@code null} {@link TrackedRace#getStartOfRace() start time}. Being "connected" here means
|
||||
* that the race is linked to a {@link Leaderboard} that is part of a {@link LeaderboardGroup} which is in turn
|
||||
* {@link Event#getLeaderboardGroups() linked} to the {@link Event}.
|
||||
*
|
||||
* @return a new map whose keys identify the race and whose values have a short info about the race that will allow,
|
||||
* e.g., to sort by start time and therefore identify "anniversary" races in a central instance. All
|
||||
* {@link SimpleRaceInfo#getRemoteUrl()} values will be {@code null}, meaning that the tracked races live
|
||||
* locally on this server. Callers may modify the map as each call to this method will produce a new copy.
|
||||
*/
|
||||
Map<RegattaAndRaceIdentifier, SimpleRaceInfo> getLocalRaceList();
|
||||
|
||||
/**
|
||||
* Provides a {@link DetailedRaceInfo} for the given {@link RegattaAndRaceIdentifier}. The algorithm first tries to
|
||||
* resolve this via {@link getFullDetailsForRaceLocal(RegattaAndRaceIdentifier)}, if no local result can be
|
||||
* determined, the identifier is resolved against the cached remote race list in the
|
||||
* {@link com.sap.sailing.server.impl.RemoteSailingServerSet}. If a match is found, the remoteUrl stored in the
|
||||
* match is used to make a remote REST call to retrieve the required information from the remote server. This method
|
||||
* is intended to be used to resolve detailed information for determined anniversary races.
|
||||
*
|
||||
* @return a DetailedRaceInfo object or null if the race could not be resolved
|
||||
*/
|
||||
DetailedRaceInfo getFullDetailsForRaceCascading(RegattaAndRaceIdentifier regattaNameAndRaceName);
|
||||
|
||||
/**
|
||||
* Provides a {@link DetailedRaceInfo} for the given {@link RegattaAndRaceIdentifier}. This method only tries to
|
||||
* resolve the race against locally tracked races reachable from an event that have a startOfRace that is not
|
||||
* {@code null}.
|
||||
*
|
||||
* @return a DetailedRaceInfo object or null if the race could not be resolved
|
||||
*/
|
||||
DetailedRaceInfo getFullDetailsForRaceLocal(RegattaAndRaceIdentifier raceIdentifier);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class Activator implements BundleActivator {
|
||||
private SailingNotificationServiceImpl notificationService;
|
||||
|
||||
private ServiceTracker<MailService, MailService> mailServiceTracker;
|
||||
|
||||
|
||||
public Activator() {
|
||||
clearPersistentCompetitors = Boolean
|
||||
.valueOf(System.getProperty(CLEAR_PERSISTENT_COMPETITORS_PROPERTY_NAME, "" + false));
|
||||
@@ -115,10 +115,12 @@ public class Activator implements BundleActivator {
|
||||
// this code block is not run, and the test case can inject some other type of finder
|
||||
// instead.
|
||||
serviceFinderFactory = new CachedOsgiTypeBasedServiceFinderFactory(context);
|
||||
|
||||
racingEventService = new RacingEventServiceImpl(clearPersistentCompetitors, serviceFinderFactory,
|
||||
trackedRegattaListener, notificationService, trackedRaceStatisticsCache, restoreTrackedRaces);
|
||||
notificationService.setRacingEventService(racingEventService);
|
||||
|
||||
|
||||
masterDataImportClassLoaderServiceTracker = new ServiceTracker<MasterDataImportClassLoaderService, MasterDataImportClassLoaderService>(
|
||||
context, MasterDataImportClassLoaderService.class,
|
||||
new MasterDataImportClassLoaderServiceTrackerCustomizer(context, racingEventService));
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ public class RaceLogScoringReplicator implements RaceColumnListenerWithDefaultAc
|
||||
* If a non-{@code null} {@code optionalExplicitScore} is provided, it it returned. Otherwise, if a valid
|
||||
* {@link MaxPointsReason} is provided, {@code null} is returned as the explicit score because the scoring scheme
|
||||
* shall be used dynamically to determine the penalty score (see
|
||||
* {@link ScoringScheme#getPenaltyScore(RaceColumn, Competitor, MaxPointsReason, Integer, com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher, TimePoint)}).
|
||||
* {@link ScoringScheme#getPenaltyScore(RaceColumn, Competitor, MaxPointsReason, Integer, com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher, TimePoint, Leaderboard)}).
|
||||
* If no explicit score is provided and no {@link MaxPointsReason} is specified, a score is determined from the
|
||||
* {@code oneBasedRankByRaceCommittee} using the leaderboard scoring scheme's
|
||||
* {@link ScoringScheme#getScoreForRank(Leaderboard, RaceColumn, Competitor, int, java.util.concurrent.Callable, com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher, TimePoint)
|
||||
|
||||
+96
@@ -58,6 +58,8 @@ import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
|
||||
import com.sap.sailing.domain.anniversary.DetailedRaceInfo;
|
||||
import com.sap.sailing.domain.anniversary.SimpleRaceInfo;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.CompetitorStore;
|
||||
import com.sap.sailing.domain.base.CompetitorStore.CompetitorUpdateListener;
|
||||
@@ -3816,4 +3818,98 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
|
||||
statisticsAggregators.forEach((year, aggregator) -> result.put(year, aggregator.getStatistics()));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<RegattaAndRaceIdentifier, SimpleRaceInfo> getRemoteRaceList() {
|
||||
final HashMap<RegattaAndRaceIdentifier, SimpleRaceInfo> store = new HashMap<>();
|
||||
for (Entry<RemoteSailingServerReference, Pair<Iterable<SimpleRaceInfo>, Exception>> race : remoteSailingServerSet
|
||||
.getCachedRaceList().entrySet()) {
|
||||
if (race.getValue().getB() != null) {
|
||||
throw new RuntimeException("Some remoteserver did not respond " + race.getKey());
|
||||
}
|
||||
for (SimpleRaceInfo raceinfo : race.getValue().getA()) {
|
||||
store.put(raceinfo.getIdentifier(), raceinfo);
|
||||
}
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<RegattaAndRaceIdentifier, SimpleRaceInfo> getLocalRaceList() {
|
||||
final HashMap<RegattaAndRaceIdentifier, SimpleRaceInfo> store = new HashMap<>();
|
||||
for (Event event : getAllEvents()) {
|
||||
for (LeaderboardGroup group : event.getLeaderboardGroups()) {
|
||||
for (Leaderboard leaderboard : group.getLeaderboards()) {
|
||||
for (RaceColumn race : leaderboard.getRaceColumns()) {
|
||||
for (Fleet fleet : race.getFleets()) {
|
||||
TrackedRace trackedRace = race.getTrackedRace(fleet);
|
||||
if (trackedRace != null) {
|
||||
RegattaAndRaceIdentifier raceIdentifier = trackedRace.getRaceIdentifier();
|
||||
final TimePoint startOfRace = trackedRace.getStartOfRace();
|
||||
if (startOfRace != null) {
|
||||
SimpleRaceInfo raceInfo = new SimpleRaceInfo(raceIdentifier, startOfRace, /* remoteURL */ null);
|
||||
store.put(raceInfo.getIdentifier(), raceInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DetailedRaceInfo getFullDetailsForRaceLocal(RegattaAndRaceIdentifier raceIdentifier) {
|
||||
DetailedRaceInfo bestMatch = null;
|
||||
// start from the top; while there are more efficient ways to look up the TrackedRace by its
|
||||
// race identifier, this wouldn't tell a valid event and leaderboard combination through which
|
||||
// to navigate to it
|
||||
for (Event event : this.getAllEvents()) {
|
||||
for (LeaderboardGroup group : event.getLeaderboardGroups()) {
|
||||
for (Leaderboard leaderboard : group.getLeaderboards()) {
|
||||
for (RaceColumn race : leaderboard.getRaceColumns()) {
|
||||
for (Fleet fleet : race.getFleets()) {
|
||||
TrackedRace trackedRace = race.getTrackedRace(fleet);
|
||||
if (trackedRace != null) {
|
||||
RegattaAndRaceIdentifier trackedRaceIdentifier = trackedRace.getRaceIdentifier();
|
||||
// check if the race matches the RegattaAndRaceIdentifier
|
||||
if (trackedRaceIdentifier.equals(raceIdentifier)
|
||||
&& trackedRace.getStartOfRace() != null) {
|
||||
// check if the match is a best match -> we keep the previous match otherwise
|
||||
if (bestMatch == null
|
||||
|| (leaderboard.getName().equals(trackedRaceIdentifier.getRegattaName()))) {
|
||||
bestMatch = new DetailedRaceInfo(trackedRaceIdentifier, leaderboard.getName(),
|
||||
trackedRace.getStartOfRace(), event.getId(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DetailedRaceInfo getFullDetailsForRaceCascading(RegattaAndRaceIdentifier raceIdentifier) {
|
||||
DetailedRaceInfo bestMatch = getFullDetailsForRaceLocal(raceIdentifier);
|
||||
if (bestMatch == null) {
|
||||
// check for stored simpleRaceInfo from remote server
|
||||
Map<RemoteSailingServerReference, Pair<Iterable<SimpleRaceInfo>, Exception>> races = remoteSailingServerSet.getCachedRaceList();
|
||||
for (Entry<RemoteSailingServerReference, Pair<Iterable<SimpleRaceInfo>, Exception>> cachedInfo : races.entrySet()) {
|
||||
Iterable<SimpleRaceInfo> raceList = cachedInfo.getValue().getA();
|
||||
if (raceList != null) {
|
||||
for (SimpleRaceInfo race : raceList) {
|
||||
if (race.getIdentifier().equals(raceIdentifier)) {
|
||||
bestMatch = remoteSailingServerSet.getDetailedInfoBlocking(race);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestMatch;
|
||||
}
|
||||
}
|
||||
|
||||
+112
-3
@@ -3,6 +3,7 @@ package com.sap.sailing.server.impl;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
@@ -26,6 +27,8 @@ import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
||||
import com.sap.sailing.domain.anniversary.DetailedRaceInfo;
|
||||
import com.sap.sailing.domain.anniversary.SimpleRaceInfo;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.EventBase;
|
||||
import com.sap.sailing.domain.base.RemoteSailingServerReference;
|
||||
@@ -37,6 +40,8 @@ import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJ
|
||||
import com.sap.sailing.server.gateway.deserialization.impl.StatisticsByYearJsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.deserialization.impl.StatisticsJsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.DetailedRaceInfoJsonSerializer;
|
||||
import com.sap.sailing.server.gateway.serialization.impl.SimpleRaceInfoJsonSerializer;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.concurrent.LockUtil;
|
||||
@@ -66,6 +71,8 @@ public class RemoteSailingServerSet {
|
||||
|
||||
private final ConcurrentMap<RemoteSailingServerReference, Util.Pair<Map<Integer, Statistics>, Exception>> cachedStatisticsByYearForRemoteSailingServers;
|
||||
private final StatisticsJsonDeserializer statisticsJsonDeserializer;
|
||||
|
||||
private final ConcurrentMap<RemoteSailingServerReference, Util.Pair<Iterable<SimpleRaceInfo>, Exception>> cachedTrackedRacesForRemoteSailingServers;
|
||||
|
||||
/**
|
||||
* @param scheduler
|
||||
@@ -76,6 +83,7 @@ public class RemoteSailingServerSet {
|
||||
remoteSailingServers = new ConcurrentHashMap<>();
|
||||
cachedEventsForRemoteSailingServers = new ConcurrentHashMap<>();
|
||||
cachedStatisticsByYearForRemoteSailingServers = new ConcurrentHashMap<>();
|
||||
cachedTrackedRacesForRemoteSailingServers = new ConcurrentHashMap<>();
|
||||
scheduler.scheduleAtFixedRate(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -89,6 +97,7 @@ public class RemoteSailingServerSet {
|
||||
remoteSailingServers.clear();
|
||||
cachedEventsForRemoteSailingServers.clear();
|
||||
cachedStatisticsByYearForRemoteSailingServers.clear();
|
||||
cachedTrackedRacesForRemoteSailingServers.clear();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,6 +128,67 @@ public class RemoteSailingServerSet {
|
||||
new Thread(() -> updateRemoteServerEventCacheSynchronously(ref), "Event Cache Updater for remote server " + ref).start();
|
||||
new Thread(() -> updateRemoteServerStatisticsCacheSynchronously(ref),
|
||||
"Statistics by year Cache Updater for remote server " + ref).start();
|
||||
new Thread(() -> updateRemoteServerTrackedRacesCacheSynchronously(ref), "Anniversary Cache Updater for remote server " + ref).start();
|
||||
}
|
||||
|
||||
private void updateRemoteServerTrackedRacesCacheSynchronously(RemoteSailingServerReference ref) {
|
||||
BufferedReader bufferedReader = null;
|
||||
Util.Pair<Iterable<SimpleRaceInfo>, Exception> result;
|
||||
try {
|
||||
try {
|
||||
final URL raceListURL = getRaceListURL(ref.getURL());
|
||||
logger.fine("Updating racelist for remote server " + ref + " from URL " + raceListURL);
|
||||
URLConnection urlConnection = HttpUrlConnectionHelper.redirectConnection(raceListURL);
|
||||
bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
|
||||
SimpleRaceInfoJsonSerializer deserializer = new SimpleRaceInfoJsonSerializer();
|
||||
final Set<SimpleRaceInfo> races = new HashSet<>();
|
||||
JSONParser parser = new JSONParser();
|
||||
JSONArray racesGroupedByRemoteAsObject = (JSONArray) parser.parse(bufferedReader);
|
||||
for (Object remoteWithRaces : racesGroupedByRemoteAsObject) {
|
||||
JSONObject remoteWithRacesAsJson = (JSONObject) remoteWithRaces;
|
||||
String remoteUrlAsString = (String) remoteWithRacesAsJson.get(DetailedRaceInfoJsonSerializer.FIELD_REMOTEURL);
|
||||
URL remoteUrl;
|
||||
if (remoteUrlAsString != null && !remoteUrlAsString.isEmpty()) {
|
||||
remoteUrl = new URL(remoteUrlAsString);
|
||||
} else {
|
||||
// the race was local to the remote server, indicated by a null URL; use the remote reference's URL for those
|
||||
remoteUrl = ref.getURL();
|
||||
}
|
||||
JSONArray raceListForOneRemote = (JSONArray) remoteWithRacesAsJson.get(DetailedRaceInfoJsonSerializer.FIELD_RACES);
|
||||
for (Object remoteRace : raceListForOneRemote) {
|
||||
JSONObject remoteRaceAsJson = (JSONObject) remoteRace;
|
||||
SimpleRaceInfo event = deserializer.deserialize(remoteRaceAsJson, remoteUrl);
|
||||
races.add(event);
|
||||
}
|
||||
}
|
||||
result = new Util.Pair<Iterable<SimpleRaceInfo>, Exception>(races, /* exception */ null);
|
||||
} finally {
|
||||
if (bufferedReader != null) {
|
||||
bufferedReader.close();
|
||||
}
|
||||
}
|
||||
} catch (IOException | ParseException e) {
|
||||
logger.log(Level.INFO, "Exception trying to fetch AnniversaryRaceData from remote server " + ref + ": " + e.getMessage(),
|
||||
e);
|
||||
result = new Util.Pair<Iterable<SimpleRaceInfo>, Exception>(/* events */ null, e);
|
||||
}
|
||||
final Pair<Iterable<SimpleRaceInfo>, Exception> finalResult = result;
|
||||
LockUtil.executeWithWriteLock(lock, () -> {
|
||||
// check that the server was not removed while no lock was held
|
||||
if (remoteSailingServers.containsValue(ref)) {
|
||||
cachedTrackedRacesForRemoteSailingServers.put(ref, finalResult);
|
||||
} else {
|
||||
logger.fine("Omitted update for " + ref + " as it was removed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private URL getRaceListURL(URL remoteServerBaseURL) throws MalformedURLException {
|
||||
return getEndpointUrl(remoteServerBaseURL, "/trackedRaces/getRaces?transitive=true");
|
||||
}
|
||||
|
||||
private URL getEndpointUrl(URL remoteServerBaseURL, final String endpoint) throws MalformedURLException {
|
||||
return getURL(remoteServerBaseURL, "sailingserver/api/v1"+endpoint);
|
||||
}
|
||||
|
||||
private Util.Pair<Iterable<EventBase>, Exception> updateRemoteServerEventCacheSynchronously(
|
||||
@@ -213,11 +283,18 @@ public class RemoteSailingServerSet {
|
||||
}
|
||||
|
||||
private URL getEventsURL(URL remoteServerBaseURL) throws MalformedURLException {
|
||||
return getURL(remoteServerBaseURL, "sailingserver/api/v1/events");
|
||||
return getEndpointUrl(remoteServerBaseURL, "/events");
|
||||
}
|
||||
|
||||
private URL getDetailRaceInfoURL(URL remoteServerBaseURL, SimpleRaceInfo matching) throws MalformedURLException, UnsupportedEncodingException {
|
||||
String encodedRaceName = java.net.URLEncoder.encode(matching.getIdentifier().getRaceName(), "UTF-8").replace("+", "%20");
|
||||
String encodedRegattaName = java.net.URLEncoder.encode(matching.getIdentifier().getRegattaName(), "UTF-8").replace("+", "%20");
|
||||
String params = "raceName="+encodedRaceName+"®attaName="+encodedRegattaName;
|
||||
return getEndpointUrl(remoteServerBaseURL, "/trackedRaces/raceDetails?"+params);
|
||||
}
|
||||
|
||||
private URL getStatisticsByYearURL(URL remoteServerBaseURL) throws MalformedURLException {
|
||||
return getURL(remoteServerBaseURL, "sailingserver/api/v1/statistics/years");
|
||||
return getEndpointUrl(remoteServerBaseURL, "/statistics/years");
|
||||
}
|
||||
|
||||
private URL getURL(URL remoteServerBaseURL, String subURL) throws MalformedURLException {
|
||||
@@ -244,9 +321,10 @@ public class RemoteSailingServerSet {
|
||||
if (ref != null) {
|
||||
cachedEventsForRemoteSailingServers.remove(ref);
|
||||
cachedStatisticsByYearForRemoteSailingServers.remove(ref);
|
||||
cachedTrackedRacesForRemoteSailingServers.remove(ref);
|
||||
}
|
||||
return ref;
|
||||
}finally{
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(lock);
|
||||
}
|
||||
}
|
||||
@@ -291,4 +369,35 @@ public class RemoteSailingServerSet {
|
||||
LockUtil.unlockAfterRead(lock);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<RemoteSailingServerReference, Pair<Iterable<SimpleRaceInfo>, Exception>> getCachedRaceList() {
|
||||
LockUtil.lockForRead(lock);
|
||||
try {
|
||||
return Collections.unmodifiableMap(cachedTrackedRacesForRemoteSailingServers);
|
||||
} finally {
|
||||
LockUtil.unlockAfterRead(lock);
|
||||
}
|
||||
}
|
||||
|
||||
public DetailedRaceInfo getDetailedInfoBlocking(SimpleRaceInfo matching) {
|
||||
try {
|
||||
URL remoteRequestUrl = getDetailRaceInfoURL(matching.getRemoteUrl(),matching);
|
||||
logger.fine("Loading DetailedRace data for remote server from URL " + remoteRequestUrl);
|
||||
URLConnection urlConnection = HttpUrlConnectionHelper.redirectConnection(remoteRequestUrl);
|
||||
try (BufferedReader bufferedReader = new BufferedReader(
|
||||
new InputStreamReader(urlConnection.getInputStream(), "UTF-8"))) {
|
||||
JSONParser parser = new JSONParser();
|
||||
JSONObject detailedInfoAsJson = (JSONObject) parser.parse(bufferedReader);
|
||||
DetailedRaceInfoJsonSerializer detailedRaceListJsonSerializer = new DetailedRaceInfoJsonSerializer();
|
||||
DetailedRaceInfo detailedInfoAsJava = detailedRaceListJsonSerializer.deserialize(detailedInfoAsJson);
|
||||
if (detailedInfoAsJava.getRemoteUrl() == null) {
|
||||
// not transitive, use direct url
|
||||
detailedInfoAsJava = new DetailedRaceInfo(detailedInfoAsJava, matching.getRemoteUrl());
|
||||
}
|
||||
return detailedInfoAsJava;
|
||||
}
|
||||
} catch (ParseException | IOException e) {
|
||||
throw new IllegalStateException("RemoteUrl could not be called ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -154,7 +154,7 @@ public class SimulationServiceImpl implements SimulationService {
|
||||
protected void defaultAction() {
|
||||
if ((!this.covered)&&(legIdentifier!=null)) {
|
||||
this.covered = true;
|
||||
LegIdentifier tmpLegIdentifier = new LegIdentifierImpl(legIdentifier, legIdentifier.getLegName());
|
||||
LegIdentifier tmpLegIdentifier = new LegIdentifierImpl(legIdentifier.getRaceIdentifier(), legIdentifier.getLegName());
|
||||
scheduler.schedule(() -> triggerUpdate(tmpLegIdentifier), WAIT_MILLIS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
@@ -204,7 +204,9 @@ public class SimulationServiceImpl implements SimulationService {
|
||||
@Override
|
||||
public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
|
||||
// relevant for simulation
|
||||
// TODO: identify influenced legs and update these legs
|
||||
if (this.isLive()) {
|
||||
defaultAction();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -328,6 +330,7 @@ public class SimulationServiceImpl implements SimulationService {
|
||||
SimulationResults result = null;
|
||||
TrackedRace trackedRace = racingEventService.getTrackedRace(legIdentifier);
|
||||
if (trackedRace != null) {
|
||||
boolean isLive = trackedRace.isLive(simulationStartTime);
|
||||
int legNumber = legIdentifier.getLegNumber();
|
||||
Course raceCourse = trackedRace.getRace().getCourse();
|
||||
Leg leg = raceCourse.getLegs().get(legNumber);
|
||||
@@ -347,6 +350,8 @@ public class SimulationServiceImpl implements SimulationService {
|
||||
}
|
||||
if (markPassing != null) {
|
||||
startTimePoint = markPassing.getTimePoint();
|
||||
} else if (isLive && (legNumber == 0)) {
|
||||
startTimePoint = simulationStartTime;
|
||||
}
|
||||
markPassingIterator = trackedRace.getMarkPassingsInOrder(toWaypoint).iterator();
|
||||
if (markPassingIterator.hasNext()) {
|
||||
@@ -361,6 +366,9 @@ public class SimulationServiceImpl implements SimulationService {
|
||||
if ((startTimePoint != null) && (endTimePoint != null)) {
|
||||
legDuration = endTimePoint.asMillis() - startTimePoint.asMillis();
|
||||
}
|
||||
if (isLive && (markPassing == null)) {
|
||||
endTimePoint = simulationStartTime;
|
||||
}
|
||||
Position startPosition = null;
|
||||
List<Position> startLine = null;
|
||||
Position endPosition = null;
|
||||
|
||||
+9
-4
@@ -139,10 +139,15 @@ public class TrackedRaceStatisticsCacheImpl extends AbstractTrackedRegattaAndRac
|
||||
}
|
||||
}
|
||||
// We can be sure that either a cache update was triggered or no further updated will be triggered
|
||||
// We now wait for any result to be updated so that no new entry is generated after removing the entry
|
||||
cache.get(trackedRace, true);
|
||||
// it's now safe to remove the entry without the risk that it is added again later
|
||||
cache.remove(trackedRace);
|
||||
// We now wait for any result to be updated so that no new entry is generated after removing the entry.
|
||||
// The triggerUpdate will at least have been called once.
|
||||
final Thread t = new Thread(()->{
|
||||
cache.get(trackedRace, true);
|
||||
// it's now safe to remove the entry without the risk that it is added again later
|
||||
cache.remove(trackedRace);
|
||||
}, "Cache cleaner for tracked race "+trackedRace.getRace().getName());
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private class Updater extends AbstractCacheUpdater<TrackedRace, TrackedRaceStatistics, EmptyUpdateInterval> {
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
to obtain a true wind speed and direction. ZIP files are searched for .txt files which
|
||||
are then analyzed.
|
||||
</li>
|
||||
<li>A new scoring scheme "Low Point with Automatic RDG" is now available. It can help in league
|
||||
set-ups where one possible definition for the score of a redress given can be to average the
|
||||
scores of the competitor in all other flights scored, for past and coming races. This scoring
|
||||
scheme will apply as a default score for an RDG this average. As usual, as soon as an explicit
|
||||
score is provided for the race, the default calculation will no longer be applied.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 class="articleSubheadline">July 2017</h2>
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ public abstract class AbstractParallelProcessor<InputType, ResultType> extends A
|
||||
try {
|
||||
executor.execute(instruction);
|
||||
} catch (RejectedExecutionException exc) {
|
||||
LOGGER.log(Level.WARNING, "A " + RejectedExecutionException.class.getSimpleName()
|
||||
LOGGER.log(Level.FINEST, "A " + RejectedExecutionException.class.getSimpleName()
|
||||
+ " appeared during the processing.");
|
||||
instruction.run();
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ public interface ThreadPoolUtil {
|
||||
*/
|
||||
ScheduledExecutorService createBackgroundTaskThreadPoolExecutor(String name);
|
||||
|
||||
ScheduledExecutorService createBackgroundTaskThreadPoolExecutor(int size, String name);
|
||||
|
||||
/**
|
||||
* In case an application module really requires its own thread pool instead of
|
||||
* using {@link #getBackgroundTaskThreadPool}, it should size the thread pool
|
||||
|
||||
@@ -30,6 +30,11 @@ public class ThreadPoolUtilImpl implements ThreadPoolUtil {
|
||||
return createThreadPoolExecutor(name, Thread.NORM_PRIORITY-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledExecutorService createBackgroundTaskThreadPoolExecutor(int size, String name) {
|
||||
return createThreadPoolExecutor(name, Thread.NORM_PRIORITY-1, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledExecutorService createForegroundTaskThreadPoolExecutor(String name) {
|
||||
return createThreadPoolExecutor(name, Thread.NORM_PRIORITY);
|
||||
|
||||
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: TracTrac Client module
|
||||
Bundle-SymbolicName: com.tractrac.clientmodule
|
||||
Bundle-Version: 3.7.5
|
||||
Bundle-Version: 3.7.6
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Bundle-ClassPath: .,
|
||||
lib/TracAPI.jar
|
||||
|
||||
@@ -16,6 +16,28 @@ It contains also some files:
|
||||
- test.sh -> script that compiles the code in the src folder, creates the test.jar library and execute the code of the example.
|
||||
- Manifest.txt -> manifest used to create the test.jar file
|
||||
|
||||
********************************************
|
||||
************* TracAPI 3.7.6 ****************
|
||||
********************************************
|
||||
This is a final version.It fixes bugs in the implementation and it adds a some features.
|
||||
It keeps the backward compatibility.
|
||||
|
||||
Release date: 14/08/2017
|
||||
Build number: 14241
|
||||
|
||||
1) Features
|
||||
|
||||
- Two new RaceCompetitorStatusType values have been added: 8 (Did not finish) and 9 (Missing data). (Requested by
|
||||
Chris Terkelsen, 28/07/2017)
|
||||
|
||||
2) Bugs
|
||||
|
||||
- Releasing the memory when the ISubscriber.close method is called (Reported by Axel Uhl, 04/08/2017)
|
||||
- The live messages (e.g: race start time updated, live delay updated...) are not arriving sometimes,
|
||||
despite of the server is sending them all the time. The implementation of this part has been
|
||||
downgraded to the version used in TracAPI 3.6.x (Reported by SAP)
|
||||
|
||||
|
||||
********************************************
|
||||
************* TracAPI 3.7.5 ****************
|
||||
********************************************
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -8,6 +8,6 @@
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>com.tractrac.clientmodule</artifactId>
|
||||
<version>3.7.5</version>
|
||||
<version>3.7.6</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
</project>
|
||||
|
||||
Reference in New Issue
Block a user