mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-06 04:13:51 +00:00
Merge branch 'master' into test
Conflicts: java/pom.xml
This commit is contained in:
commit
326c3f72ba
52 files changed
+886
-1650
No files matched your search
@@ -1,8 +1,7 @@
|
|||||||
|
|
||||||
Axel
|
Axel
|
||||||
====
|
====
|
||||||
|
|
||||||
- Implement and test course update with corresponding TrackedLeg[OfCompetitor] updates
|
- What about distance traveled in first leg? It seems to jump too high at the beginning.
|
||||||
|
|
||||||
- Open test server firewall ports for UDP / Expedition wind integration
|
- Open test server firewall ports for UDP / Expedition wind integration
|
||||||
|
|
||||||
@@ -10,10 +9,6 @@ Axel
|
|||||||
|
|
||||||
- Set up two Expedition machines with RF and UMTS transmission, test killing one
|
- Set up two Expedition machines with RF and UMTS transmission, test killing one
|
||||||
|
|
||||||
- Test duplicate addrace for the same race; consider cleaning cache when stopRace/stopEvent is invoked
|
|
||||||
|
|
||||||
- Test duplicate addevent for the same event
|
|
||||||
|
|
||||||
- Test fail-over in case one of the two Java VM fails
|
- Test fail-over in case one of the two Java VM fails
|
||||||
|
|
||||||
- enable switching between our and TracTrac-provided leaderboard information
|
- enable switching between our and TracTrac-provided leaderboard information
|
||||||
@@ -22,6 +17,10 @@ Axel
|
|||||||
|
|
||||||
- Display gain/loss for next-best competitor (green/red arrow up/down) based on VMG comparison
|
- Display gain/loss for next-best competitor (green/red arrow up/down) based on VMG comparison
|
||||||
|
|
||||||
|
- Now that we can update Course's waypoints, consider creating a RaceDefinition right upon
|
||||||
|
receiving the Event/Race from TracTrac, using an empty list of waypoints. This may simplify
|
||||||
|
the entire life cycle of RaceDefinition objects.
|
||||||
|
|
||||||
- Improve Mongo-based tests by using Mongo transactions instead of waiting for prior transactions to complete
|
- Improve Mongo-based tests by using Mongo transactions instead of waiting for prior transactions to complete
|
||||||
|
|
||||||
|
|
||||||
@@ -30,8 +29,6 @@ Simon
|
|||||||
|
|
||||||
+ Server: Mehr RAM bestellen
|
+ Server: Mehr RAM bestellen
|
||||||
+ Prüfen, warum in der Map Anzeige manchmal ein Competitor doppelt ist
|
+ Prüfen, warum in der Map Anzeige manchmal ein Competitor doppelt ist
|
||||||
+ Simulator mit Speed = 1 anschmeissen und dann prüfen was passiert
|
+ ANCHOR anzeigen für große Werte (Bei DSTTRV raussuchen was da z.B. groß ist)
|
||||||
+ Prüfen warum im live leaderbaord manchmal 4 legs angezeigt werden (max ist 2+1)
|
|
||||||
+ Prüfen warum der Listener thread manchmal nicht richtig gestoppt wird
|
|
||||||
+ Implementierung Leaderboard auf Basis HTML Designer
|
+ Implementierung Leaderboard auf Basis HTML Designer
|
||||||
+ Wenn Axel Branches eingerichtet hat, dann MongoDBs auf Server startbar machen über supervisord
|
+ Wenn Axel Branches eingerichtet hat, dann MongoDBs auf Server startbar machen über supervisord
|
||||||
+1
-1
@@ -55,7 +55,7 @@ public class DeclinationImportTest extends AbstractDeclinationTest {
|
|||||||
public void readOnlineOrFromFile() throws IOException, ClassNotFoundException, ParseException {
|
public void readOnlineOrFromFile() throws IOException, ClassNotFoundException, ParseException {
|
||||||
Declination declination = importer.getDeclination(new DegreePosition(53, 3),
|
Declination declination = importer.getDeclination(new DegreePosition(53, 3),
|
||||||
new MillisecondsTimePoint(simpleDateFormat.parse("2011-05-27").getTime()),
|
new MillisecondsTimePoint(simpleDateFormat.parse("2011-05-27").getTime()),
|
||||||
/* timeoutForOnlineFetchInMilliseconds */ 5000);
|
/* timeoutForOnlineFetchInMilliseconds */ 10000);
|
||||||
assertNotNull(declination);
|
assertNotNull(declination);
|
||||||
System.out.println(declination);
|
System.out.println(declination);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#Fri Jun 10 21:39:55 CEST 2011
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
encoding//src/com/sap/sailing/domain/test/WindTest.java=UTF-8
|
||||||
+10
-2
@@ -67,7 +67,7 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
|
|||||||
super.setUp();
|
super.setUp();
|
||||||
domainFactory = new DomainFactoryImpl();
|
domainFactory = new DomainFactoryImpl();
|
||||||
domainEvent = domainFactory.createEvent(getEvent());
|
domainEvent = domainFactory.createEvent(getEvent());
|
||||||
trackedEvent = domainFactory.trackEvent(domainEvent);
|
trackedEvent = domainFactory.getOrCreateTrackedEvent(domainEvent);
|
||||||
receivers.add(new RaceCourseReceiver(domainFactory, trackedEvent, getEvent(), /* millisecondsOverWhichToAverageWind */
|
receivers.add(new RaceCourseReceiver(domainFactory, trackedEvent, getEvent(), /* millisecondsOverWhichToAverageWind */
|
||||||
EmptyWindStore.INSTANCE,
|
EmptyWindStore.INSTANCE,
|
||||||
30000, /* millisecondsOverWhichToAverageSpeed */30000) {
|
30000, /* millisecondsOverWhichToAverageSpeed */30000) {
|
||||||
@@ -81,7 +81,10 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
addListenersForStoredDataAndStartController(receivers);
|
addListenersForStoredDataAndStartController(receivers);
|
||||||
race = domainFactory.getRaceDefinition(getEvent().getRaceList().iterator().next());
|
Race tractracRace = getEvent().getRaceList().iterator().next();
|
||||||
|
// now we expect that there is no
|
||||||
|
assertNull(domainFactory.getExistingRaceDefinitionForRace(tractracRace));
|
||||||
|
race = domainFactory.getRaceDefinition(tractracRace);
|
||||||
course = race.getCourse();
|
course = race.getCourse();
|
||||||
assertNotNull(course);
|
assertNotNull(course);
|
||||||
assertEquals(3, Util.size(course.getWaypoints()));
|
assertEquals(3, Util.size(course.getWaypoints()));
|
||||||
@@ -91,6 +94,11 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
|
|||||||
assertEquals("top", course.getLegs().get(1).getFrom().getName());
|
assertEquals("top", course.getLegs().get(1).getFrom().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertNull(RaceDefinition existingRaceDefinitionForRace) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the race course's legs have corresponding {@link TrackedLeg}s and the {@link TrackedLeg}s have
|
* Asserts that the race course's legs have corresponding {@link TrackedLeg}s and the {@link TrackedLeg}s have
|
||||||
* {@link TrackedLegOfCompetitor} for each of the race's competitors.
|
* {@link TrackedLegOfCompetitor} for each of the race's competitors.
|
||||||
|
|||||||
+1
-1
@@ -73,7 +73,7 @@ public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest {
|
|||||||
List<Receiver> receivers = new ArrayList<Receiver>();
|
List<Receiver> receivers = new ArrayList<Receiver>();
|
||||||
receivers.add(receiver);
|
receivers.add(receiver);
|
||||||
for (Receiver r : DomainFactory.INSTANCE.getUpdateReceivers(
|
for (Receiver r : DomainFactory.INSTANCE.getUpdateReceivers(
|
||||||
DomainFactory.INSTANCE.trackEvent(DomainFactory.INSTANCE.createEvent(getEvent())),
|
DomainFactory.INSTANCE.getOrCreateTrackedEvent(DomainFactory.INSTANCE.createEvent(getEvent())),
|
||||||
getEvent(), EmptyWindStore.INSTANCE, ReceiverType.RACECOURSE,
|
getEvent(), EmptyWindStore.INSTANCE, ReceiverType.RACECOURSE,
|
||||||
ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS)) {
|
ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS)) {
|
||||||
receivers.add(r);
|
receivers.add(r);
|
||||||
|
|||||||
+4
-1
@@ -72,13 +72,16 @@ public class ReceiveTrackingDataTest extends AbstractTracTracLiveTest {
|
|||||||
};
|
};
|
||||||
List<TypeController> listeners = new ArrayList<TypeController>();
|
List<TypeController> listeners = new ArrayList<TypeController>();
|
||||||
Event event = domainFactory.createEvent(getEvent());
|
Event event = domainFactory.createEvent(getEvent());
|
||||||
DynamicTrackedEvent trackedEvent = domainFactory.trackEvent(event);
|
DynamicTrackedEvent trackedEvent = domainFactory.getOrCreateTrackedEvent(event);
|
||||||
trackedEvent.addRaceListener(new RaceListener() {
|
trackedEvent.addRaceListener(new RaceListener() {
|
||||||
@Override
|
@Override
|
||||||
public void raceAdded(TrackedRace trackedRace) {
|
public void raceAdded(TrackedRace trackedRace) {
|
||||||
System.out.println("Subscribing raw position listener for race "+trackedRace);
|
System.out.println("Subscribing raw position listener for race "+trackedRace);
|
||||||
((DynamicTrackedRace) trackedRace).addListener(positionListener);
|
((DynamicTrackedRace) trackedRace).addListener(positionListener);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public void raceRemoved(TrackedRace trackedRace) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
for (Receiver receiver : domainFactory.getUpdateReceivers(trackedEvent, getEvent(), EmptyWindStore.INSTANCE)) {
|
for (Receiver receiver : domainFactory.getUpdateReceivers(trackedEvent, getEvent(), EmptyWindStore.INSTANCE)) {
|
||||||
for (TypeController raceListener : receiver.getTypeControllers()) {
|
for (TypeController raceListener : receiver.getTypeControllers()) {
|
||||||
|
|||||||
@@ -224,4 +224,15 @@ public class TrackTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDistanceTraveledOnInBetweenSectionFromFixToFix() {
|
||||||
|
// take second and third fix and compute distance between them
|
||||||
|
Iterator<GPSFixMoving> iter = track.getFixes().iterator();
|
||||||
|
iter.next(); // skip first;
|
||||||
|
GPSFix second = iter.next();
|
||||||
|
GPSFix third = iter.next();
|
||||||
|
assertEquals(second.getPosition().getDistance(third.getPosition()),
|
||||||
|
track.getDistanceTraveled(second.getTimePoint(), third.getTimePoint()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+17
-2
@@ -76,7 +76,11 @@ public interface DomainFactory {
|
|||||||
RaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException,
|
RaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException,
|
||||||
FileNotFoundException, URISyntaxException;
|
FileNotFoundException, URISyntaxException;
|
||||||
|
|
||||||
DynamicTrackedEvent trackEvent(com.sap.sailing.domain.base.Event event);
|
/**
|
||||||
|
* Looks for tracking information about <code>event</code>. If no such object exists yet, a new one
|
||||||
|
* is created.
|
||||||
|
*/
|
||||||
|
DynamicTrackedEvent getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event event);
|
||||||
|
|
||||||
BoatClass getBoatClass(CompetitorClass competitorClass);
|
BoatClass getBoatClass(CompetitorClass competitorClass);
|
||||||
|
|
||||||
@@ -93,7 +97,7 @@ public interface DomainFactory {
|
|||||||
*
|
*
|
||||||
* @param trackedEvent
|
* @param trackedEvent
|
||||||
* must have been created before through
|
* must have been created before through
|
||||||
* {@link #trackEvent(com.sap.sailing.domain.base.Event)} because
|
* {@link #getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event)} because
|
||||||
* otherwise the link to the {@link Event} can't be established
|
* otherwise the link to the {@link Event} can't be established
|
||||||
* @param windStore TODO
|
* @param windStore TODO
|
||||||
*/
|
*/
|
||||||
@@ -117,6 +121,11 @@ public interface DomainFactory {
|
|||||||
DynamicTrackedRace trackRace(TrackedEvent trackedEvent, RaceDefinition raceDefinition,
|
DynamicTrackedRace trackRace(TrackedEvent trackedEvent, RaceDefinition raceDefinition,
|
||||||
WindStore windStore, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, Event tractracEvent);
|
WindStore windStore, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, Event tractracEvent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-blocking call that returns <code>null</code> if the {@link RaceDefinition} for the TracTrac Event
|
||||||
|
* hasn't been created yet, e.g., because the course definition hasn't been received yet or the listener
|
||||||
|
* for receiving course information hasn't been registered (yet).
|
||||||
|
*/
|
||||||
RaceDefinition getRace(Event tractracEvent);
|
RaceDefinition getRace(Event tractracEvent);
|
||||||
|
|
||||||
JSONService parseJSONURL(URL jsonURL) throws IOException, ParseException, org.json.simple.parser.ParseException;
|
JSONService parseJSONURL(URL jsonURL) throws IOException, ParseException, org.json.simple.parser.ParseException;
|
||||||
@@ -133,4 +142,10 @@ public interface DomainFactory {
|
|||||||
* in a course. A single control point may be used more than once in a course's list of waypoints.
|
* in a course. A single control point may be used more than once in a course's list of waypoints.
|
||||||
*/
|
*/
|
||||||
void updateCourseWaypoints(Course courseToUpdate, List<ControlPoint> controlPoints) throws PatchFailedException;
|
void updateCourseWaypoints(Course courseToUpdate, List<ControlPoint> controlPoints) throws PatchFailedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks for the tracking information for <code>event</code>. If not found, <code>null</code> is returned
|
||||||
|
* immediately. See also {@link #getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event)}.
|
||||||
|
*/
|
||||||
|
DynamicTrackedEvent getTrackedEvent(com.sap.sailing.domain.base.Event event);
|
||||||
}
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package com.sap.sailing.domain.tractracadapter;
|
||||||
|
|
||||||
|
import com.sap.sailing.domain.base.Event;
|
||||||
|
import com.sap.sailing.domain.base.RaceDefinition;
|
||||||
|
import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
|
||||||
|
import com.sap.sailing.domain.tractracadapter.impl.RaceCourseReceiver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives clients a way to refer to a specific race even though the {@link RaceDefinition} object may not have
|
||||||
|
* been created yet, but without exposing the TracTrac internals. The {@link #getRace()} call will block
|
||||||
|
* until the {@link RaceDefinition} has been created.
|
||||||
|
*
|
||||||
|
* @author Axel Uhl (d043530)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface RaceHandle {
|
||||||
|
|
||||||
|
Event getEvent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the race definition. If the race definition represented by this handle hasn't been created yet, the call
|
||||||
|
* blocks until such a definition is provided by another call, usually by the {@link RaceCourseReceiver}.
|
||||||
|
*/
|
||||||
|
RaceDefinition getRace();
|
||||||
|
|
||||||
|
DynamicTrackedEvent getTrackedEvent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tracker managing the tracking of the race to which this is a handle.
|
||||||
|
*/
|
||||||
|
RaceTracker getRaceTracker();
|
||||||
|
|
||||||
|
}
|
||||||
+24
@@ -2,15 +2,39 @@ package com.sap.sailing.domain.tractracadapter;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
import com.sap.sailing.domain.base.RaceDefinition;
|
import com.sap.sailing.domain.base.RaceDefinition;
|
||||||
|
import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
|
||||||
|
import com.sap.sailing.domain.tracking.TrackedEvent;
|
||||||
|
import com.sap.sailing.domain.tracking.WindStore;
|
||||||
|
import com.sap.sailing.util.Util.Triple;
|
||||||
|
|
||||||
public interface RaceTracker {
|
public interface RaceTracker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops tracking the race and removes the {@link TrackedRace} object one gets from calling
|
||||||
|
* {@link #getTrackedEvent()}.{@link TrackedEvent#getTrackedRace(RaceDefinition) getTrackedRace(}{@link #getRace() getRace())}
|
||||||
|
* from the {@link #getTrackedEvent() tracked event}.
|
||||||
|
*/
|
||||||
void stop() throws MalformedURLException, IOException, InterruptedException;
|
void stop() throws MalformedURLException, IOException, InterruptedException;
|
||||||
|
|
||||||
com.sap.sailing.domain.base.Event getEvent();
|
com.sap.sailing.domain.base.Event getEvent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-blocking call that returns <code>null</code> if the {@link RaceDefinition} for the TracTrac Event
|
||||||
|
* hasn't been created yet, e.g., because the course definition hasn't been received yet or the listener
|
||||||
|
* for receiving course information hasn't been registered (yet).
|
||||||
|
*/
|
||||||
RaceDefinition getRace();
|
RaceDefinition getRace();
|
||||||
|
|
||||||
|
RaceHandle getRaceHandle();
|
||||||
|
|
||||||
|
DynamicTrackedEvent getTrackedEvent();
|
||||||
|
|
||||||
|
WindStore getWindStore();
|
||||||
|
|
||||||
|
Triple<URL, URI, URI> getURLs();
|
||||||
|
|
||||||
}
|
}
|
||||||
+7
-2
@@ -74,7 +74,7 @@ import difflib.PatchFailedException;
|
|||||||
public class DomainFactoryImpl implements DomainFactory {
|
public class DomainFactoryImpl implements DomainFactory {
|
||||||
private static final Logger logger = Logger.getLogger(DomainFactoryImpl.class.getName());
|
private static final Logger logger = Logger.getLogger(DomainFactoryImpl.class.getName());
|
||||||
|
|
||||||
private final long millisecondsOverWhichToAverageSpeed = 5000;
|
private final long millisecondsOverWhichToAverageSpeed = 15000;
|
||||||
|
|
||||||
// TODO clarify how millisecondsOverWhichToAverageWind could be updated and propagated live
|
// TODO clarify how millisecondsOverWhichToAverageWind could be updated and propagated live
|
||||||
private final long millisecondsOverWhichToAverageWind = 30000;
|
private final long millisecondsOverWhichToAverageWind = 30000;
|
||||||
@@ -340,7 +340,12 @@ public class DomainFactoryImpl implements DomainFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DynamicTrackedEvent trackEvent(com.sap.sailing.domain.base.Event event) {
|
public DynamicTrackedEvent getTrackedEvent(com.sap.sailing.domain.base.Event event) {
|
||||||
|
return eventTrackingCache.get(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DynamicTrackedEvent getOrCreateTrackedEvent(com.sap.sailing.domain.base.Event event) {
|
||||||
synchronized (eventTrackingCache) {
|
synchronized (eventTrackingCache) {
|
||||||
DynamicTrackedEvent result = eventTrackingCache.get(event);
|
DynamicTrackedEvent result = eventTrackingCache.get(event);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
|
|||||||
+11
-4
@@ -84,19 +84,26 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<Route, RouteDa
|
|||||||
// Therefore, don't create TrackedRace again because it already exists.
|
// Therefore, don't create TrackedRace again because it already exists.
|
||||||
try {
|
try {
|
||||||
getDomainFactory().updateCourseWaypoints(course, event.getB().getPoints());
|
getDomainFactory().updateCourseWaypoints(course, event.getB().getPoints());
|
||||||
|
if (trackedEvent.getExistingTrackedRace(existingRaceDefinitionForRace) == null) {
|
||||||
|
createTrackedRace(existingRaceDefinitionForRace);
|
||||||
|
}
|
||||||
} catch (PatchFailedException e) {
|
} catch (PatchFailedException e) {
|
||||||
logger.log(Level.SEVERE, "Internal error updating race course "+course+": "+e.getMessage());
|
logger.log(Level.SEVERE, "Internal error updating race course "+course+": "+e.getMessage());
|
||||||
logger.throwing(RaceCourseReceiver.class.getName(), "handleEvent", e);
|
logger.throwing(RaceCourseReceiver.class.getName(), "handleEvent", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.log(Level.INFO, "Received course for non-existing race "+event.getC().getName()+". Creating RaceDefinition.");
|
logger.log(Level.INFO, "Received course for non-existing race "+event.getC().getName()+". Creating RaceDefinition.");
|
||||||
// create race redefinition
|
// create race definition
|
||||||
RaceDefinition raceDefinition = getDomainFactory().createRaceDefinition(event.getC(), course);
|
RaceDefinition raceDefinition = getDomainFactory().createRaceDefinition(event.getC(), course);
|
||||||
trackedEvent.getEvent().addRace(raceDefinition);
|
trackedEvent.getEvent().addRace(raceDefinition);
|
||||||
DynamicTrackedRace trackedRace = getDomainFactory().trackRace(trackedEvent, raceDefinition,
|
createTrackedRace(raceDefinition);
|
||||||
windStore, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, tractracEvent);
|
|
||||||
trackedEvent.addTrackedRace(trackedRace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createTrackedRace(RaceDefinition race) {
|
||||||
|
DynamicTrackedRace trackedRace = getDomainFactory().trackRace(trackedEvent, race,
|
||||||
|
windStore, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, tractracEvent);
|
||||||
|
trackedEvent.addTrackedRace(trackedRace);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package com.sap.sailing.domain.tractracadapter.impl;
|
||||||
|
|
||||||
|
import com.sap.sailing.domain.base.RaceDefinition;
|
||||||
|
import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
|
||||||
|
import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
||||||
|
import com.sap.sailing.domain.tractracadapter.RaceHandle;
|
||||||
|
import com.sap.sailing.domain.tractracadapter.RaceTracker;
|
||||||
|
import com.tractrac.clientmodule.Event;
|
||||||
|
|
||||||
|
public class RaceHandleImpl implements RaceHandle {
|
||||||
|
private final Event tractracEvent;
|
||||||
|
private final DomainFactory domainFactory;
|
||||||
|
private final DynamicTrackedEvent trackedEvent;
|
||||||
|
private final RaceTracker raceTracker;
|
||||||
|
|
||||||
|
public RaceHandleImpl(DomainFactory domainFactory, Event tractracEvent, DynamicTrackedEvent trackedEvent, RaceTracker raceTracker) {
|
||||||
|
this.domainFactory = domainFactory;
|
||||||
|
this.tractracEvent = tractracEvent;
|
||||||
|
this.trackedEvent = trackedEvent;
|
||||||
|
this.raceTracker = raceTracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public com.sap.sailing.domain.base.Event getEvent() {
|
||||||
|
return domainFactory.createEvent(tractracEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RaceDefinition getRace() {
|
||||||
|
// we assume there is exactly one Race per TracTrac event
|
||||||
|
return domainFactory.getRaceDefinition(tractracEvent.getRaceList().iterator().next());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DynamicTrackedEvent getTrackedEvent() {
|
||||||
|
return trackedEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RaceTracker getRaceTracker() {
|
||||||
|
return raceTracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+42
-10
@@ -16,10 +16,13 @@ import com.sap.sailing.domain.base.RaceDefinition;
|
|||||||
import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
|
import com.sap.sailing.domain.tracking.DynamicTrackedEvent;
|
||||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||||
import com.sap.sailing.domain.tracking.WindStore;
|
import com.sap.sailing.domain.tracking.WindStore;
|
||||||
|
import com.sap.sailing.domain.tracking.WindTrack;
|
||||||
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
||||||
import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
||||||
|
import com.sap.sailing.domain.tractracadapter.RaceHandle;
|
||||||
import com.sap.sailing.domain.tractracadapter.RaceTracker;
|
import com.sap.sailing.domain.tractracadapter.RaceTracker;
|
||||||
import com.sap.sailing.domain.tractracadapter.Receiver;
|
import com.sap.sailing.domain.tractracadapter.Receiver;
|
||||||
|
import com.sap.sailing.util.Util.Triple;
|
||||||
import com.tractrac.clientmodule.Event;
|
import com.tractrac.clientmodule.Event;
|
||||||
import com.tractrac.clientmodule.data.DataController;
|
import com.tractrac.clientmodule.data.DataController;
|
||||||
import com.tractrac.clientmodule.data.DataController.Listener;
|
import com.tractrac.clientmodule.data.DataController.Listener;
|
||||||
@@ -31,10 +34,18 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
|
|||||||
private final Thread ioThread;
|
private final Thread ioThread;
|
||||||
private final DataController controller;
|
private final DataController controller;
|
||||||
private final Set<Receiver> receivers;
|
private final Set<Receiver> receivers;
|
||||||
|
private final DomainFactory domainFactory;
|
||||||
|
private final DynamicTrackedEvent trackedEvent;
|
||||||
|
private final WindStore windStore;
|
||||||
|
private final Triple<URL, URI, URI> urls;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Creates a race tracked for the specified URL/URIs and starts receiving all available existing and future push
|
||||||
|
* data from there. Receiving continues until {@link #stop()} is called.
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
* A race tracker uses the <code>paramURL</code> for the TracTrac Java client to register for push data about one
|
* A race tracker uses the <code>paramURL</code> for the TracTrac Java client to register for push data about one
|
||||||
* race. The {@link RaceDefinition} for that race, however, cannot be created until the {@link Course} has been
|
* race. The {@link RaceDefinition} for that race, however, isn't created until the {@link Course} has been
|
||||||
* received. Therefore, the {@link RaceCourseReceiver} will create the {@link RaceDefinition} and will add it to the
|
* received. Therefore, the {@link RaceCourseReceiver} will create the {@link RaceDefinition} and will add it to the
|
||||||
* {@link com.sap.sailing.domain.base.Event}.
|
* {@link com.sap.sailing.domain.base.Event}.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -45,16 +56,16 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
|
|||||||
* {@link Event} as argument that is used for its tracking.
|
* {@link Event} as argument that is used for its tracking.
|
||||||
* <p>
|
* <p>
|
||||||
*
|
*
|
||||||
* When {@link #getRace} is called on this object before the {@link RaceCourseReceiver} has created the
|
|
||||||
* {@link RaceDefinition}, the call will block until this has happened.
|
|
||||||
*
|
|
||||||
* @param windStore
|
* @param windStore
|
||||||
* Provides the capability to obtain the {@link WindTrack}s for the different wind sources.
|
* Provides the capability to obtain the {@link WindTrack}s for the different wind sources. A trivial
|
||||||
* A trivial implementation is {@link EmptyWindStore} which simply provides new, empty tracks.
|
* implementation is {@link EmptyWindStore} which simply provides new, empty tracks. This is always
|
||||||
* This is always available but loses track of the wind, e.g., during server restarts.
|
* available but loses track of the wind, e.g., during server restarts.
|
||||||
*/
|
*/
|
||||||
protected RaceTrackerImpl(DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI, WindStore windStore)
|
protected RaceTrackerImpl(DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI, WindStore windStore)
|
||||||
throws URISyntaxException, MalformedURLException, FileNotFoundException {
|
throws URISyntaxException, MalformedURLException, FileNotFoundException {
|
||||||
|
urls = new Triple<URL, URI, URI>(paramURL, liveURI, storedURI);
|
||||||
|
this.windStore = windStore;
|
||||||
|
this.domainFactory = domainFactory;
|
||||||
// Read event data from configuration file
|
// Read event data from configuration file
|
||||||
tractracEvent = KeyValue.setup(paramURL);
|
tractracEvent = KeyValue.setup(paramURL);
|
||||||
|
|
||||||
@@ -69,7 +80,7 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
|
|||||||
// Start live and stored data streams
|
// Start live and stored data streams
|
||||||
ioThread = new Thread(controller, "io");
|
ioThread = new Thread(controller, "io");
|
||||||
domainEvent = domainFactory.createEvent(tractracEvent);
|
domainEvent = domainFactory.createEvent(tractracEvent);
|
||||||
DynamicTrackedEvent trackedEvent = domainFactory.trackEvent(domainEvent);
|
trackedEvent = domainFactory.getOrCreateTrackedEvent(domainEvent);
|
||||||
receivers = new HashSet<Receiver>();
|
receivers = new HashSet<Receiver>();
|
||||||
Set<TypeController> typeControllers = new HashSet<TypeController>();
|
Set<TypeController> typeControllers = new HashSet<TypeController>();
|
||||||
for (Receiver receiver : domainFactory.getUpdateReceivers(trackedEvent, tractracEvent, windStore)) {
|
for (Receiver receiver : domainFactory.getUpdateReceivers(trackedEvent, tractracEvent, windStore)) {
|
||||||
@@ -81,9 +92,29 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
|
|||||||
addListenersForStoredDataAndStartController(typeControllers);
|
addListenersForStoredDataAndStartController(typeControllers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Triple<URL, URI, URI> getURLs() {
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WindStore getWindStore() {
|
||||||
|
return windStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DynamicTrackedEvent getTrackedEvent() {
|
||||||
|
return trackedEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RaceHandle getRaceHandle() {
|
||||||
|
return new RaceHandleImpl(domainFactory, tractracEvent, getTrackedEvent(), this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RaceDefinition getRace() {
|
public RaceDefinition getRace() {
|
||||||
return DomainFactory.INSTANCE.getRace(tractracEvent);
|
return domainFactory.getRace(tractracEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addListenersForStoredDataAndStartController(Iterable<TypeController> listenersForStoredData) {
|
protected void addListenersForStoredDataAndStartController(Iterable<TypeController> listenersForStoredData) {
|
||||||
@@ -118,7 +149,8 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
|
|||||||
for (Receiver receiver : receivers) {
|
for (Receiver receiver : receivers) {
|
||||||
receiver.stop();
|
receiver.stop();
|
||||||
}
|
}
|
||||||
ioThread.join();
|
ioThread.join(3000); // wait no more than three seconds
|
||||||
|
trackedEvent.removedTrackedRace(trackedEvent.getTrackedRace(getRace()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DataController getController() {
|
protected DataController getController() {
|
||||||
|
|||||||
+10
@@ -7,6 +7,7 @@ import com.sap.sailing.domain.base.Leg;
|
|||||||
import com.sap.sailing.domain.base.Mile;
|
import com.sap.sailing.domain.base.Mile;
|
||||||
import com.sap.sailing.domain.base.Position;
|
import com.sap.sailing.domain.base.Position;
|
||||||
import com.sap.sailing.domain.base.Speed;
|
import com.sap.sailing.domain.base.Speed;
|
||||||
|
import com.sap.sailing.domain.base.SpeedWithBearing;
|
||||||
import com.sap.sailing.domain.base.TimePoint;
|
import com.sap.sailing.domain.base.TimePoint;
|
||||||
import com.sap.sailing.domain.base.impl.KilometersPerHourSpeedImpl;
|
import com.sap.sailing.domain.base.impl.KilometersPerHourSpeedImpl;
|
||||||
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
|
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
|
||||||
@@ -139,4 +140,13 @@ public class TracTracTrackedLegOfCompetitor implements TrackedLegOfCompetitor {
|
|||||||
return trackedRace;
|
return trackedRace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpeedWithBearing getSpeedOverGround(TimePoint at) {
|
||||||
|
if (hasStartedLeg(at)) {
|
||||||
|
return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(at);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+10
@@ -6,6 +6,16 @@ import com.sap.sailing.domain.base.Speed;
|
|||||||
|
|
||||||
public abstract class AbstractDistance implements Distance {
|
public abstract class AbstractDistance implements Distance {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return (o instanceof Distance) && this.compareTo((Distance) o) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 9128347 ^ (int) getMeters();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Distance o) {
|
public int compareTo(Distance o) {
|
||||||
return getMeters() > o.getMeters() ? 1 : getMeters() == o.getMeters() ? 0 : -1;
|
return getMeters() > o.getMeters() ? 1 : getMeters() == o.getMeters() ? 0 : -1;
|
||||||
|
|||||||
+2
@@ -3,5 +3,7 @@ package com.sap.sailing.domain.tracking;
|
|||||||
import com.sap.sailing.domain.base.RaceDefinition;
|
import com.sap.sailing.domain.base.RaceDefinition;
|
||||||
|
|
||||||
public interface DynamicTrackedEvent extends TrackedEvent {
|
public interface DynamicTrackedEvent extends TrackedEvent {
|
||||||
|
|
||||||
DynamicTrackedRace getTrackedRace(RaceDefinition race);
|
DynamicTrackedRace getTrackedRace(RaceDefinition race);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,4 +2,6 @@ package com.sap.sailing.domain.tracking;
|
|||||||
|
|
||||||
public interface RaceListener {
|
public interface RaceListener {
|
||||||
void raceAdded(TrackedRace trackedRace);
|
void raceAdded(TrackedRace trackedRace);
|
||||||
|
|
||||||
|
void raceRemoved(TrackedRace trackedRace);
|
||||||
}
|
}
|
||||||
@@ -19,8 +19,16 @@ public interface TrackedEvent {
|
|||||||
*/
|
*/
|
||||||
TrackedRace getTrackedRace(RaceDefinition race);
|
TrackedRace getTrackedRace(RaceDefinition race);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-blocking call that returns <code>null</code> if no tracking information currently exists
|
||||||
|
* for <code>race</code>. See also {@link #getTrackedRace(RaceDefinition)} for a blocking variant.
|
||||||
|
*/
|
||||||
|
TrackedRace getExistingTrackedRace(RaceDefinition race);
|
||||||
|
|
||||||
void addTrackedRace(TrackedRace trackedRace);
|
void addTrackedRace(TrackedRace trackedRace);
|
||||||
|
|
||||||
|
void removedTrackedRace(TrackedRace trackedRace);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener will be notified when {@link #addTrackedRace(TrackedRace)} is called and
|
* Listener will be notified when {@link #addTrackedRace(TrackedRace)} is called and
|
||||||
* upon registration for each tracked race already known. Therefore, the listener
|
* upon registration for each tracked race already known. Therefore, the listener
|
||||||
|
|||||||
+3
@@ -4,6 +4,7 @@ import com.sap.sailing.domain.base.Competitor;
|
|||||||
import com.sap.sailing.domain.base.Distance;
|
import com.sap.sailing.domain.base.Distance;
|
||||||
import com.sap.sailing.domain.base.Leg;
|
import com.sap.sailing.domain.base.Leg;
|
||||||
import com.sap.sailing.domain.base.Speed;
|
import com.sap.sailing.domain.base.Speed;
|
||||||
|
import com.sap.sailing.domain.base.SpeedWithBearing;
|
||||||
import com.sap.sailing.domain.base.TimePoint;
|
import com.sap.sailing.domain.base.TimePoint;
|
||||||
|
|
||||||
public interface TrackedLegOfCompetitor {
|
public interface TrackedLegOfCompetitor {
|
||||||
@@ -91,4 +92,6 @@ public interface TrackedLegOfCompetitor {
|
|||||||
*/
|
*/
|
||||||
Double getEstimatedTimeToNextMarkInSeconds(TimePoint timePoint) throws NoWindException;
|
Double getEstimatedTimeToNextMarkInSeconds(TimePoint timePoint) throws NoWindException;
|
||||||
|
|
||||||
|
SpeedWithBearing getSpeedOverGround(TimePoint at);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -137,8 +137,7 @@ public interface TrackedRace {
|
|||||||
WindTrack getWindTrack(WindSource windSource);
|
WindTrack getWindTrack(WindSource windSource);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits until {@link #getUpdateCount()} is after <code>since</code>.
|
* Waits until {@link #getUpdateCount()} is after <code>sinceUpdate</code>.
|
||||||
* @param sinceUpdate TODO
|
|
||||||
*/
|
*/
|
||||||
void waitForNextUpdate(int sinceUpdate) throws InterruptedException;
|
void waitForNextUpdate(int sinceUpdate) throws InterruptedException;
|
||||||
|
|
||||||
@@ -149,4 +148,10 @@ public interface TrackedRace {
|
|||||||
NavigableSet<MarkPassing> getMarkPassings(Competitor competitor);
|
NavigableSet<MarkPassing> getMarkPassings(Competitor competitor);
|
||||||
|
|
||||||
void removeWind(Wind wind, WindSource windSource);
|
void removeWind(Wind wind, WindSource windSource);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time stamp that the last event received from the underlying push service carried on it.
|
||||||
|
* Note that these times may not increase monotonically.
|
||||||
|
*/
|
||||||
|
TimePoint getTimePointOfLastEvent();
|
||||||
}
|
}
|
||||||
+3
@@ -119,6 +119,9 @@ public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements
|
|||||||
competitorMarkPassings.add(markPassing);
|
competitorMarkPassings.add(markPassing);
|
||||||
getMarkPassingsInOrder(markPassing.getWaypoint()).add(markPassing);
|
getMarkPassingsInOrder(markPassing.getWaypoint()).add(markPassing);
|
||||||
updated(markPassing.getTimePoint());
|
updated(markPassing.getTimePoint());
|
||||||
|
}
|
||||||
|
// notify *after* all mark passings have been re-established; should avoid flicker
|
||||||
|
for (MarkPassing markPassing : markPassings) {
|
||||||
notifyListeners(markPassing);
|
notifyListeners(markPassing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
@@ -48,6 +48,24 @@ public class TrackedEventImpl implements TrackedEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removedTrackedRace(TrackedRace trackedRace) {
|
||||||
|
synchronized (trackedRaces) {
|
||||||
|
trackedRaces.remove(trackedRace.getRace());
|
||||||
|
Collection<TrackedRace> trbbc = trackedRacesByBoatClass.get(trackedRace.getRace().getBoatClass());
|
||||||
|
if (trbbc != null) {
|
||||||
|
trbbc.remove(trackedRace);
|
||||||
|
if (trbbc.isEmpty()) {
|
||||||
|
trbbc.remove(trackedRace.getRace().getBoatClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (RaceListener listener : raceListeners) {
|
||||||
|
listener.raceRemoved(trackedRace);
|
||||||
|
}
|
||||||
|
trackedRaces.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Event getEvent() {
|
public Event getEvent() {
|
||||||
return event;
|
return event;
|
||||||
@@ -80,6 +98,11 @@ public class TrackedEventImpl implements TrackedEvent {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TrackedRace getExistingTrackedRace(RaceDefinition race) {
|
||||||
|
return trackedRaces.get(race);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addRaceListener(RaceListener listener) {
|
public void addRaceListener(RaceListener listener) {
|
||||||
raceListeners.add(listener);
|
raceListeners.add(listener);
|
||||||
|
|||||||
+9
-6
@@ -77,7 +77,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
|||||||
if (legStart == null) {
|
if (legStart == null) {
|
||||||
return Distance.NULL;
|
return Distance.NULL;
|
||||||
} else {
|
} else {
|
||||||
MarkPassing legEnd = getTrackedRace().getMarkPassing(getCompetitor(), getLeg().getTo());
|
MarkPassing legEnd = getMarkPassingForLegEnd();
|
||||||
TimePoint end = timePoint;
|
TimePoint end = timePoint;
|
||||||
if (legEnd != null && timePoint.compareTo(legEnd.getTimePoint()) > 0) {
|
if (legEnd != null && timePoint.compareTo(legEnd.getTimePoint()) > 0) {
|
||||||
// timePoint is after leg finish; take leg end and end time point
|
// timePoint is after leg finish; take leg end and end time point
|
||||||
@@ -245,7 +245,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
|||||||
public Speed getAverageVelocityMadeGood(TimePoint timePoint) throws NoWindException {
|
public Speed getAverageVelocityMadeGood(TimePoint timePoint) throws NoWindException {
|
||||||
Speed result = null;
|
Speed result = null;
|
||||||
MarkPassing start = getMarkPassingForLegStart();
|
MarkPassing start = getMarkPassingForLegStart();
|
||||||
if (start != null && start.getTimePoint().compareTo(timePoint) > 0) {
|
if (start != null && start.getTimePoint().compareTo(timePoint) <= 0) {
|
||||||
MarkPassing end = getMarkPassingForLegEnd();
|
MarkPassing end = getMarkPassingForLegEnd();
|
||||||
if (end != null) {
|
if (end != null) {
|
||||||
TimePoint to;
|
TimePoint to;
|
||||||
@@ -254,13 +254,15 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
|||||||
} else {
|
} else {
|
||||||
to = timePoint;
|
to = timePoint;
|
||||||
}
|
}
|
||||||
Position endPos = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(to, false);
|
Position endPos = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(to, /* extrapolate */ false);
|
||||||
|
if (endPos != null) {
|
||||||
Distance d = getWindwardDistance(
|
Distance d = getWindwardDistance(
|
||||||
getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(start.getTimePoint(), false), endPos,
|
getTrackedRace().getTrack(getCompetitor())
|
||||||
to);
|
.getEstimatedPosition(start.getTimePoint(), false), endPos, to);
|
||||||
result = d.inTime(to.asMillis() - start.getTimePoint().asMillis());
|
result = d.inTime(to.asMillis() - start.getTimePoint().asMillis());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +361,8 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpeedWithBearing getSpeedOverGround(TimePoint at) {
|
@Override
|
||||||
|
public SpeedWithBearing getSpeedOverGround(TimePoint at) {
|
||||||
if (hasStartedLeg(at)) {
|
if (hasStartedLeg(at)) {
|
||||||
return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(at);
|
return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(at);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+7
@@ -46,6 +46,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
|||||||
private TimePoint start;
|
private TimePoint start;
|
||||||
private TimePoint finish;
|
private TimePoint finish;
|
||||||
private TimePoint timePointOfNewestEvent;
|
private TimePoint timePointOfNewestEvent;
|
||||||
|
private TimePoint timePointOfLastEvent;
|
||||||
private int updateCount;
|
private int updateCount;
|
||||||
|
|
||||||
private final Map<TimePoint, TreeSet<Competitor>> competitorRankings;
|
private final Map<TimePoint, TreeSet<Competitor>> competitorRankings;
|
||||||
@@ -353,6 +354,11 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
|||||||
return timePointOfNewestEvent;
|
return timePointOfNewestEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimePoint getTimePointOfLastEvent() {
|
||||||
|
return timePointOfLastEvent;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param timeOfEvent may be <code>null</code> meaning to only unblock waiters but not update any time points
|
* @param timeOfEvent may be <code>null</code> meaning to only unblock waiters but not update any time points
|
||||||
*/
|
*/
|
||||||
@@ -366,6 +372,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
|||||||
if (startOfTracking == null || startOfTracking.compareTo(timeOfEvent) > 0) {
|
if (startOfTracking == null || startOfTracking.compareTo(timeOfEvent) > 0) {
|
||||||
startOfTracking = timeOfEvent;
|
startOfTracking = timeOfEvent;
|
||||||
}
|
}
|
||||||
|
timePointOfLastEvent = timeOfEvent;
|
||||||
}
|
}
|
||||||
notifyAll();
|
notifyAll();
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -243,4 +243,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TimePoint getTimePointOfLastEvent() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+2
-1
@@ -160,7 +160,8 @@ public class UDPExpeditionReceiverTest {
|
|||||||
assertEquals(19, cal.get(Calendar.DAY_OF_MONTH));
|
assertEquals(19, cal.get(Calendar.DAY_OF_MONTH));
|
||||||
assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
|
assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
|
||||||
assertEquals(52, cal.get(Calendar.MINUTE));
|
assertEquals(52, cal.get(Calendar.MINUTE));
|
||||||
assertEquals(46, cal.get(Calendar.SECOND));
|
// for some bizarre reason the conversion doesn't seem to be predictable; a calendar / platform issue?
|
||||||
|
assertTrue(cal.get(Calendar.SECOND) == 46 || cal.get(Calendar.SECOND) == 45);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ public class TestStoringAndRetrievingWindTracks extends AbstractTracTracLiveTest
|
|||||||
public void testStoreAFewWindEntries() throws UnknownHostException, MongoException, InterruptedException {
|
public void testStoreAFewWindEntries() throws UnknownHostException, MongoException, InterruptedException {
|
||||||
DomainFactory domainFactory = DomainFactory.INSTANCE;
|
DomainFactory domainFactory = DomainFactory.INSTANCE;
|
||||||
Event domainEvent = domainFactory.createEvent(getEvent());
|
Event domainEvent = domainFactory.createEvent(getEvent());
|
||||||
DynamicTrackedEvent trackedEvent = domainFactory.trackEvent(domainEvent);
|
DynamicTrackedEvent trackedEvent = domainFactory.getOrCreateTrackedEvent(domainEvent);
|
||||||
Iterable<Receiver> typeControllers = domainFactory.getUpdateReceivers(trackedEvent, getEvent(),
|
Iterable<Receiver> typeControllers = domainFactory.getUpdateReceivers(trackedEvent, getEvent(),
|
||||||
EmptyWindStore.INSTANCE, ReceiverType.RACECOURSE);
|
EmptyWindStore.INSTANCE, ReceiverType.RACECOURSE);
|
||||||
addListenersForStoredDataAndStartController(typeControllers);
|
addListenersForStoredDataAndStartController(typeControllers);
|
||||||
|
|||||||
+1
-1
@@ -65,7 +65,7 @@ public class MongoWindStoreFactoryImpl implements MongoWindStoreFactory, BundleA
|
|||||||
}
|
}
|
||||||
defaultHostName = context.getProperty(MONGO_HOSTNAME);
|
defaultHostName = context.getProperty(MONGO_HOSTNAME);
|
||||||
if (defaultHostName == null) {
|
if (defaultHostName == null) {
|
||||||
defaultHostName = "localhost";
|
defaultHostName = "127.0.0.1";
|
||||||
} else {
|
} else {
|
||||||
logger.log(Level.INFO, "found mongo.hostname="+defaultHostName);
|
logger.log(Level.INFO, "found mongo.hostname="+defaultHostName);
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
|
||||||
|
<classpathentry kind="src" path="src"/>
|
||||||
|
<classpathentry kind="output" path="bin"/>
|
||||||
|
</classpath>
|
||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
/bin
|
||||||
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>com.sap.sailing.server.test</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.pde.ManifestBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.pde.SchemaBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.pde.PluginNature</nature>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#Mon Jun 13 17:15:33 CEST 2011
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||||
|
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||||
|
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||||
|
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||||
|
org.eclipse.jdt.core.compiler.source=1.6
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#Mon Jun 13 17:15:33 CEST 2011
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
pluginProject.extensions=false
|
||||||
|
resolve.requirebundle=false
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
Manifest-Version: 1.0
|
||||||
|
Bundle-ManifestVersion: 2
|
||||||
|
Bundle-Name: Test
|
||||||
|
Bundle-SymbolicName: com.sap.sailing.server.test
|
||||||
|
Bundle-Version: 1.0.0.qualifier
|
||||||
|
Bundle-Vendor: SAP
|
||||||
|
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
|
||||||
|
Require-Bundle: com.sap.sailing.server,
|
||||||
|
com.sap.sailing.domain,
|
||||||
|
com.sap.sailing.domain.tractracadapter,
|
||||||
|
org.junit4
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
source.. = src/
|
||||||
|
output.. = bin/
|
||||||
|
bin.includes = META-INF/,\
|
||||||
|
.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<artifactId>root</artifactId>
|
||||||
|
<groupId>com.sap.sailing</groupId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<groupId>com.sap.sailing</groupId>
|
||||||
|
<artifactId>com.sap.sailing.server.test</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<packaging>eclipse-test-plugin</packaging>
|
||||||
|
</project>
|
||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
package com.sap.sailing.server.test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNotSame;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.sap.sailing.domain.base.RaceDefinition;
|
||||||
|
import com.sap.sailing.domain.tracking.RaceListener;
|
||||||
|
import com.sap.sailing.domain.tracking.TrackedEvent;
|
||||||
|
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||||
|
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
||||||
|
import com.sap.sailing.domain.tractracadapter.RaceHandle;
|
||||||
|
import com.sap.sailing.server.RacingEventServiceImpl;
|
||||||
|
|
||||||
|
public class RaceTrackerTest {
|
||||||
|
private final URL paramUrl;
|
||||||
|
private final URI liveUri;
|
||||||
|
private final URI storedUri;
|
||||||
|
private RacingEventServiceImpl service;
|
||||||
|
private RaceHandle raceHandle;
|
||||||
|
|
||||||
|
public RaceTrackerTest() throws MalformedURLException, URISyntaxException {
|
||||||
|
// for live simulation:
|
||||||
|
// paramUrl = new URL("http://sapsimulation.tracdev.dk/simulateconf/j80race12.txt");
|
||||||
|
// liveUri = new URI("tcp://sapsimulation.tracdev.dk:4420"); // or with tunneling: tcp://localhost:4420
|
||||||
|
// storedUri = new URI("tcp://sapsimulation.tracdev.dk:4421"); // or with tunneling: tcp://localhost:4421
|
||||||
|
// for stored race, non-real-time simulation:
|
||||||
|
paramUrl = new URL("http://germanmaster.traclive.dk/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=bd8c778e-7c65-11e0-8236-406186cbf87c");
|
||||||
|
|
||||||
|
// tunneled:
|
||||||
|
//liveUri = new URI("tcp://localhost:4412");
|
||||||
|
//storedUri = new URI("tcp://localhost:4413");
|
||||||
|
|
||||||
|
//no tunnel:
|
||||||
|
liveUri = new URI("tcp://germanmaster.traclive.dk:4400");
|
||||||
|
storedUri = new URI("tcp://germanmaster.traclive.dk:4401");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws MalformedURLException, FileNotFoundException, URISyntaxException, InterruptedException {
|
||||||
|
service = new RacingEventServiceImpl();
|
||||||
|
raceHandle = service.addRace(paramUrl, liveUri, storedUri, EmptyWindStore.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TrackedRace getTrackedRace(TrackedEvent trackedEvent) throws InterruptedException {
|
||||||
|
final TrackedRace[] trackedRaces = new TrackedRace[1];
|
||||||
|
trackedEvent.addRaceListener(new RaceListener() {
|
||||||
|
@Override
|
||||||
|
public void raceAdded(TrackedRace trackedRace) {
|
||||||
|
synchronized (trackedRaces) {
|
||||||
|
trackedRaces[0] = trackedRace;
|
||||||
|
trackedRaces.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void raceRemoved(TrackedRace trackedRace) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
synchronized (trackedRaces) {
|
||||||
|
if (trackedRaces[0] == null) {
|
||||||
|
trackedRaces.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trackedRaces[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitialization() throws InterruptedException {
|
||||||
|
RaceDefinition race = raceHandle.getRace();
|
||||||
|
assertNotNull(race);
|
||||||
|
assertNotNull(getTrackedRace(raceHandle.getTrackedEvent()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStopTracking() throws MalformedURLException, IOException, InterruptedException, URISyntaxException {
|
||||||
|
TrackedEvent oldTrackedEvent = raceHandle.getTrackedEvent();
|
||||||
|
TrackedRace oldTrackedRace = getTrackedRace(oldTrackedEvent);
|
||||||
|
service.stopTracking(raceHandle.getEvent());
|
||||||
|
RaceHandle myRaceHandle = service.addRace(paramUrl, liveUri, storedUri, EmptyWindStore.INSTANCE);
|
||||||
|
TrackedEvent newTrackedEvent = myRaceHandle.getTrackedEvent();
|
||||||
|
TrackedRace newTrackedRace = getTrackedRace(newTrackedEvent);
|
||||||
|
// expecting a new tracked race to be created when starting over with tracking
|
||||||
|
assertNotSame(oldTrackedRace, newTrackedRace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test asserts that tracking the same race twice doesn't create another tracker and in particular no
|
||||||
|
* new tracked event / tracked race.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testTrackingSameRaceWithoutStopping() throws MalformedURLException, IOException, InterruptedException, URISyntaxException {
|
||||||
|
TrackedEvent oldTrackedEvent = raceHandle.getTrackedEvent();
|
||||||
|
TrackedRace oldTrackedRace = getTrackedRace(oldTrackedEvent);
|
||||||
|
RaceHandle myRaceHandle = service.addRace(paramUrl, liveUri, storedUri, EmptyWindStore.INSTANCE);
|
||||||
|
TrackedEvent newTrackedEvent = myRaceHandle.getTrackedEvent();
|
||||||
|
TrackedRace newTrackedRace = getTrackedRace(newTrackedEvent);
|
||||||
|
// expecting a new tracked race to be created when starting over with tracking
|
||||||
|
assertSame(oldTrackedRace, newTrackedRace);
|
||||||
|
assertSame(raceHandle.getRaceTracker(), myRaceHandle.getRaceTracker());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
</listAttribute>
|
</listAttribute>
|
||||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console"/>
|
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console"/>
|
||||||
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
|
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
|
||||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dorg.osgi.service.http.port=8888"/>
|
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dorg.osgi.service.http.port=8886"/>
|
||||||
<stringAttribute key="pde.version" value="3.3"/>
|
<stringAttribute key="pde.version" value="3.3"/>
|
||||||
<booleanAttribute key="show_selected_only" value="false"/>
|
<booleanAttribute key="show_selected_only" value="false"/>
|
||||||
<stringAttribute key="target_bundles" value="javax.servlet.jsp@default:default,javax.servlet@default:default,org.apache.ant@default:default,org.apache.commons.el@default:default,org.apache.commons.logging@default:default,org.apache.jasper@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.runtime.compatibility.registry@default:false,org.eclipse.equinox.common@2:true,org.eclipse.equinox.http.jetty@default:default,org.eclipse.equinox.http.registry@default:default,org.eclipse.equinox.http.servlet@default:default,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.util@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi.util@default:default,org.eclipse.osgi@-1:true,org.hamcrest.core@default:default,org.mortbay.jetty.server@default:default,org.mortbay.jetty.util@default:default"/>
|
<stringAttribute key="target_bundles" value="javax.servlet.jsp@default:default,javax.servlet@default:default,org.apache.ant@default:default,org.apache.commons.el@default:default,org.apache.commons.logging@default:default,org.apache.jasper@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.runtime.compatibility.registry@default:false,org.eclipse.equinox.common@2:true,org.eclipse.equinox.http.jetty@default:default,org.eclipse.equinox.http.registry@default:default,org.eclipse.equinox.http.servlet@default:default,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.util@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi.util@default:default,org.eclipse.osgi@-1:true,org.hamcrest.core@default:default,org.mortbay.jetty.server@default:default,org.mortbay.jetty.util@default:default"/>
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ public class AdminApp extends Servlet {
|
|||||||
if (race == null) {
|
if (race == null) {
|
||||||
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
|
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
|
||||||
} else {
|
} else {
|
||||||
TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race);
|
TrackedRace trackedRace = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race);
|
||||||
TimePoint time = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now());
|
TimePoint time = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now());
|
||||||
TimePoint oneHourLater = new MillisecondsTimePoint(time.asMillis()+3600*1000);
|
TimePoint oneHourLater = new MillisecondsTimePoint(time.asMillis()+3600*1000);
|
||||||
String[] latitudes = req.getParameterValues(PARAM_NAME_LATDEG);
|
String[] latitudes = req.getParameterValues(PARAM_NAME_LATDEG);
|
||||||
@@ -202,7 +202,7 @@ public class AdminApp extends Servlet {
|
|||||||
if (race == null) {
|
if (race == null) {
|
||||||
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
|
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
|
||||||
} else {
|
} else {
|
||||||
TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race);
|
TrackedRace trackedRace = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race);
|
||||||
TimePoint from = getTimePoint(req, PARAM_NAME_FROM_TIME, PARAM_NAME_FROM_TIME_MILLIS,
|
TimePoint from = getTimePoint(req, PARAM_NAME_FROM_TIME, PARAM_NAME_FROM_TIME_MILLIS,
|
||||||
trackedRace.getStart()==null?new MillisecondsTimePoint(0):
|
trackedRace.getStart()==null?new MillisecondsTimePoint(0):
|
||||||
/* 24h before race start */ new MillisecondsTimePoint(trackedRace.getStart().asMillis()-24*3600*1000));
|
/* 24h before race start */ new MillisecondsTimePoint(trackedRace.getStart().asMillis()-24*3600*1000));
|
||||||
@@ -257,7 +257,7 @@ public class AdminApp extends Servlet {
|
|||||||
if (race == null) {
|
if (race == null) {
|
||||||
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
|
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
|
||||||
} else {
|
} else {
|
||||||
TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event)
|
TrackedRace trackedRace = getService().getDomainFactory().getTrackedEvent(event)
|
||||||
.getTrackedRace(race);
|
.getTrackedRace(race);
|
||||||
trackedRace.setWindSource(windSource);
|
trackedRace.setWindSource(windSource);
|
||||||
resp.getWriter().println(
|
resp.getWriter().println(
|
||||||
@@ -313,7 +313,7 @@ public class AdminApp extends Servlet {
|
|||||||
try {
|
try {
|
||||||
TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now());
|
TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now());
|
||||||
Wind wind = new WindImpl(p, timePoint, speed);
|
Wind wind = new WindImpl(p, timePoint, speed);
|
||||||
getService().getDomainFactory().trackEvent(event).getTrackedRace(race).recordWind(wind, WindSource.WEB);
|
getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race).recordWind(wind, WindSource.WEB);
|
||||||
} catch (InvalidDateException e) {
|
} catch (InvalidDateException e) {
|
||||||
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + e.getMessage());
|
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -342,7 +342,7 @@ public class AdminApp extends Servlet {
|
|||||||
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Wind source name " + sourceName + " unknown");
|
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Wind source name " + sourceName + " unknown");
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
WindTrack windTrack = getService().getDomainFactory().trackEvent(event)
|
WindTrack windTrack = getService().getDomainFactory().getTrackedEvent(event)
|
||||||
.getTrackedRace(race).getWindTrack(windSource);
|
.getTrackedRace(race).getWindTrack(windSource);
|
||||||
TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS,
|
TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS,
|
||||||
MillisecondsTimePoint.now());
|
MillisecondsTimePoint.now());
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ public class ModeratorApp extends Servlet {
|
|||||||
RaceDefinition race = getRaceDefinition(req);
|
RaceDefinition race = getRaceDefinition(req);
|
||||||
TrackedRace trackedRace = null;
|
TrackedRace trackedRace = null;
|
||||||
if (event != null && race != null) {
|
if (event != null && race != null) {
|
||||||
trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race);
|
trackedRace = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race);
|
||||||
}
|
}
|
||||||
return trackedRace;
|
return trackedRace;
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ public class ModeratorApp extends Servlet {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS,
|
TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS,
|
||||||
trackedRace.getTimePointOfNewestEvent()==null?MillisecondsTimePoint.now():trackedRace.getTimePointOfNewestEvent());
|
trackedRace.getTimePointOfLastEvent()==null?MillisecondsTimePoint.now():trackedRace.getTimePointOfLastEvent());
|
||||||
String sinceUpdateString = req.getParameter(PARAM_NAME_SINCE_UPDATE);
|
String sinceUpdateString = req.getParameter(PARAM_NAME_SINCE_UPDATE);
|
||||||
if (sinceUpdateString != null) {
|
if (sinceUpdateString != null) {
|
||||||
System.out.println("Blocking...");
|
System.out.println("Blocking...");
|
||||||
@@ -200,6 +200,8 @@ public class ModeratorApp extends Servlet {
|
|||||||
.asMillis());
|
.asMillis());
|
||||||
jsonRace.put("timeofnewestevent", trackedRace.getTimePointOfNewestEvent() == null ? 0l : trackedRace
|
jsonRace.put("timeofnewestevent", trackedRace.getTimePointOfNewestEvent() == null ? 0l : trackedRace
|
||||||
.getTimePointOfNewestEvent().asMillis());
|
.getTimePointOfNewestEvent().asMillis());
|
||||||
|
jsonRace.put("timeoflastevent", trackedRace.getTimePointOfLastEvent() == null ? 0l : trackedRace
|
||||||
|
.getTimePointOfLastEvent().asMillis());
|
||||||
jsonRace.put("updatecount", trackedRace.getUpdateCount());
|
jsonRace.put("updatecount", trackedRace.getUpdateCount());
|
||||||
Position positionForWind = null;
|
Position positionForWind = null;
|
||||||
TrackedLeg currentLeg = trackedRace.getCurrentLeg(timePoint);
|
TrackedLeg currentLeg = trackedRace.getCurrentLeg(timePoint);
|
||||||
@@ -234,6 +236,11 @@ public class ModeratorApp extends Servlet {
|
|||||||
TrackedLegOfCompetitor trackedLegOfCompetitor = leg.getTrackedLeg(competitor);
|
TrackedLegOfCompetitor trackedLegOfCompetitor = leg.getTrackedLeg(competitor);
|
||||||
if (trackedLegOfCompetitor != null) {
|
if (trackedLegOfCompetitor != null) {
|
||||||
jsonCompetitorInLeg.put("name", competitor.getName());
|
jsonCompetitorInLeg.put("name", competitor.getName());
|
||||||
|
Speed currentSpeedOverGround = trackedLegOfCompetitor.getSpeedOverGround(timePoint);
|
||||||
|
if (currentSpeedOverGround != null) {
|
||||||
|
jsonCompetitorInLeg.put("currentSpeedOverGroundInKnots",
|
||||||
|
currentSpeedOverGround == null ? null : currentSpeedOverGround.getKnots());
|
||||||
|
}
|
||||||
Speed averageSpeedOverGround = trackedLegOfCompetitor.getAverageSpeedOverGround(timePoint);
|
Speed averageSpeedOverGround = trackedLegOfCompetitor.getAverageSpeedOverGround(timePoint);
|
||||||
if (averageSpeedOverGround != null) {
|
if (averageSpeedOverGround != null) {
|
||||||
jsonCompetitorInLeg.put("averageSpeedOverGroundInKnots",
|
jsonCompetitorInLeg.put("averageSpeedOverGroundInKnots",
|
||||||
@@ -347,7 +354,7 @@ public class ModeratorApp extends Servlet {
|
|||||||
JSONObject jsonRace = new JSONObject();
|
JSONObject jsonRace = new JSONObject();
|
||||||
jsonRace.put("name", race.getName());
|
jsonRace.put("name", race.getName());
|
||||||
jsonRace.put("boatclass", race.getBoatClass()==null?"":race.getBoatClass().getName());
|
jsonRace.put("boatclass", race.getBoatClass()==null?"":race.getBoatClass().getName());
|
||||||
TimePoint start = getService().getDomainFactory().trackEvent(event).getTrackedRace(race).getStart();
|
TimePoint start = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race).getStart();
|
||||||
jsonRace.put("start", start==null?Long.MAX_VALUE:start.asMillis());
|
jsonRace.put("start", start==null?Long.MAX_VALUE:start.asMillis());
|
||||||
JSONArray jsonLegs = new JSONArray();
|
JSONArray jsonLegs = new JSONArray();
|
||||||
for (Leg leg : race.getCourse().getLegs()) {
|
for (Leg leg : race.getCourse().getLegs()) {
|
||||||
@@ -356,7 +363,7 @@ public class ModeratorApp extends Servlet {
|
|||||||
jsonLeg.put("end", leg.getTo().getName());
|
jsonLeg.put("end", leg.getTo().getName());
|
||||||
jsonLegs.add(jsonLeg);
|
jsonLegs.add(jsonLeg);
|
||||||
}
|
}
|
||||||
TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race);
|
TrackedRace trackedRace = getService().getDomainFactory().getOrCreateTrackedEvent(event).getTrackedRace(race);
|
||||||
if (trackedRace.getStart() != null) {
|
if (trackedRace.getStart() != null) {
|
||||||
jsonRace.put("start", trackedRace.getStart().asMillis());
|
jsonRace.put("start", trackedRace.getStart().asMillis());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,32 @@ import java.text.ParseException;
|
|||||||
import com.sap.sailing.declination.DeclinationService;
|
import com.sap.sailing.declination.DeclinationService;
|
||||||
import com.sap.sailing.domain.base.Event;
|
import com.sap.sailing.domain.base.Event;
|
||||||
import com.sap.sailing.domain.base.RaceDefinition;
|
import com.sap.sailing.domain.base.RaceDefinition;
|
||||||
|
import com.sap.sailing.domain.tracking.TrackedEvent;
|
||||||
|
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||||
import com.sap.sailing.domain.tracking.WindStore;
|
import com.sap.sailing.domain.tracking.WindStore;
|
||||||
import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
||||||
|
import com.sap.sailing.domain.tractracadapter.RaceHandle;
|
||||||
import com.sap.sailing.domain.tractracadapter.RaceTracker;
|
import com.sap.sailing.domain.tractracadapter.RaceTracker;
|
||||||
import com.sap.sailing.util.Util.Triple;
|
import com.sap.sailing.util.Util.Triple;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An OSGi service that can be used to track boat races using a TracTrac connector that pushes
|
||||||
|
* live GPS boat location, waypoint, coarse and mark passing data.<p>
|
||||||
|
*
|
||||||
|
* If a race/event is already being tracked, another {@link #addRace(URL, URI, URI, WindStore)} or
|
||||||
|
* {@link #addEvent(URL, URI, URI, WindStore)} call will have no effect, even if a different
|
||||||
|
* {@link WindStore} is requested.<p>
|
||||||
|
*
|
||||||
|
* TODO When the tracking of a race/event is {@link #stopTracking(Event, RaceDefinition) stopped}, the next
|
||||||
|
* time it's started to be tracked, a new {@link TrackedRace} at least will be constructed. This also
|
||||||
|
* means that when a {@link TrackedEvent} exists that still holds other {@link TrackedRace}s, the
|
||||||
|
* no longer tracked {@link TrackedRace} will be removed from the {@link TrackedEvent}.
|
||||||
|
* corresponding information is removed also from the {@link DomainFactory}'s caches to ensure that
|
||||||
|
* clean, fresh data is received should another tracking request be issued later.
|
||||||
|
*
|
||||||
|
* @author Axel Uhl (d043530)
|
||||||
|
*
|
||||||
|
*/
|
||||||
public interface RacingEventService {
|
public interface RacingEventService {
|
||||||
Iterable<Event> getAllEvents();
|
Iterable<Event> getAllEvents();
|
||||||
|
|
||||||
@@ -25,38 +46,46 @@ public interface RacingEventService {
|
|||||||
DomainFactory getDomainFactory();
|
DomainFactory getDomainFactory();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the event and for each race listed in the JSON document creates a {@link RaceTracker} that
|
* Defines the event and for each race listed in the JSON document that is not already being tracked by this service
|
||||||
* tracks the respective race. The {@link RaceDefinition}s obtained this way are all grouped into
|
* creates a {@link RaceTracker} that starts tracking the respective race. The {@link RaceDefinition}s obtained this
|
||||||
* the single {@link Event} produced for the event listed in the JSON response. Note that the
|
* way are all grouped into the single {@link Event} produced for the event listed in the JSON response. Note that
|
||||||
* many race trackers will have their TracTrac <code>Event</code> each, all with the same name,
|
* the many race trackers will have their TracTrac <code>Event</code> each, all with the same name, meaning the same
|
||||||
* meaning the same event but being distinct.
|
* event but being distinct.
|
||||||
*
|
*
|
||||||
* @param jsonURL
|
* @param jsonURL
|
||||||
* URL of a JSON response that contains an "event" object telling the event's name and ID, as well as a
|
* URL of a JSON response that contains an "event" object telling the event's name and ID, as well as a
|
||||||
* JSON array named "races" which tells ID and replay URL for the race. From those replay URLs the
|
* JSON array named "races" which tells ID and replay URL for the race. From those replay URLs the
|
||||||
* paramURL for the Java client can be derived.
|
* paramURL for the Java client can be derived.
|
||||||
* @param windStore TODO
|
|
||||||
*/
|
*/
|
||||||
void addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException,
|
Event addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException,
|
||||||
URISyntaxException, IOException, ParseException, org.json.simple.parser.ParseException;
|
URISyntaxException, IOException, ParseException, org.json.simple.parser.ParseException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a single race tracker, using the race's parameter URL which delivers the single configuration
|
* If not already tracking the URL/URI/URI combination, adds a single race tracker and starts tracking the race,
|
||||||
* text file for that race. While the result of passing this URL to the TracTrac <code>KeyValue.setup</code>
|
* using the race's parameter URL which delivers the single configuration text file for that race. While the result
|
||||||
* is a TracTrac <code>Event</code>, those events only manage a single race. In our domain model, we group
|
* of passing this URL to the TracTrac <code>KeyValue.setup</code> is a TracTrac <code>Event</code>, those events
|
||||||
* those races into a single instance of our {@link Event} class.<p>
|
* only manage a single race. In our domain model, we group those races into a single instance of our {@link Event}
|
||||||
|
* class.
|
||||||
|
* <p>
|
||||||
*
|
*
|
||||||
* If this is the first race of an event, the {@link Event} is created as well. If the {@link RaceDefinition} for
|
* If this is the first race of an event, the {@link Event} is created as well. If the {@link RaceDefinition} for
|
||||||
* the race already exists, it isn't created again. Also, if a {@link RaceTracker} for the given race already
|
* the race already exists, it isn't created again. Also, if a {@link RaceTracker} for the given race already
|
||||||
* exists, it is not added again.
|
* exists, it is not added again.<p>
|
||||||
* @param windStore TODO
|
*
|
||||||
|
* Note that when the race identified by <code>paramURL</code>, <code>liveURI</code> and <code>storedURI</code> is
|
||||||
|
* already being tracked, then regardless of the <code>windStore</code> selection the existing tracker will be used
|
||||||
|
* and its race handle will be returned. A log message will indicate a potential wind store mismatch (based on
|
||||||
|
* {@link WindStore#equals(Object)}).
|
||||||
*/
|
*/
|
||||||
void addRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException,
|
RaceHandle addRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException,
|
||||||
URISyntaxException;
|
URISyntaxException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops tracking all races of the event specified. This will also stop tracking wind for all races of this event.
|
* Stops tracking all races of the event specified. This will also stop tracking wind for all races of this event.
|
||||||
* See {@link #stopTrackingWind(Event, RaceDefinition)}.
|
* See {@link #stopTrackingWind(Event, RaceDefinition)}. If there were multiple calls to
|
||||||
|
* {@link #addRace(URL, URI, URI, WindStore)} with an equal combination of URLs/URIs, the {@link RaceTracker}
|
||||||
|
* already tracking the race was re-used. The trackers will be stopped by this call regardless of how many calls
|
||||||
|
* were made that ensured they were tracking.
|
||||||
*/
|
*/
|
||||||
void stopTracking(Event event) throws MalformedURLException, IOException, InterruptedException;
|
void stopTracking(Event event) throws MalformedURLException, IOException, InterruptedException;
|
||||||
|
|
||||||
|
|||||||
+43
-17
@@ -16,6 +16,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import com.sap.sailing.declination.DeclinationService;
|
import com.sap.sailing.declination.DeclinationService;
|
||||||
import com.sap.sailing.domain.base.Event;
|
import com.sap.sailing.domain.base.Event;
|
||||||
@@ -25,6 +26,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
|||||||
import com.sap.sailing.domain.tracking.WindStore;
|
import com.sap.sailing.domain.tracking.WindStore;
|
||||||
import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
||||||
import com.sap.sailing.domain.tractracadapter.JSONService;
|
import com.sap.sailing.domain.tractracadapter.JSONService;
|
||||||
|
import com.sap.sailing.domain.tractracadapter.RaceHandle;
|
||||||
import com.sap.sailing.domain.tractracadapter.RaceRecord;
|
import com.sap.sailing.domain.tractracadapter.RaceRecord;
|
||||||
import com.sap.sailing.domain.tractracadapter.RaceTracker;
|
import com.sap.sailing.domain.tractracadapter.RaceTracker;
|
||||||
import com.sap.sailing.expeditionconnector.UDPExpeditionReceiver;
|
import com.sap.sailing.expeditionconnector.UDPExpeditionReceiver;
|
||||||
@@ -33,11 +35,13 @@ import com.sap.sailing.util.Util.Pair;
|
|||||||
import com.sap.sailing.util.Util.Triple;
|
import com.sap.sailing.util.Util.Triple;
|
||||||
|
|
||||||
public class RacingEventServiceImpl implements RacingEventService {
|
public class RacingEventServiceImpl implements RacingEventService {
|
||||||
|
private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName());
|
||||||
|
|
||||||
private final DomainFactory domainFactory;
|
private final DomainFactory domainFactory;
|
||||||
|
|
||||||
private final Map<String, Event> eventsByName;
|
private final Map<String, Event> eventsByName;
|
||||||
|
|
||||||
private final Map<Event, Set<RaceTracker>> raceTrackers;
|
private final Map<Event, Set<RaceTracker>> raceTrackersByEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remembers the wind tracker and the port on which the UDP receiver with which the wind tracker is
|
* Remembers the wind tracker and the port on which the UDP receiver with which the wind tracker is
|
||||||
@@ -45,13 +49,19 @@ public class RacingEventServiceImpl implements RacingEventService {
|
|||||||
*/
|
*/
|
||||||
private final Map<RaceDefinition, Pair<WindTracker, Integer>> windTrackers;
|
private final Map<RaceDefinition, Pair<WindTracker, Integer>> windTrackers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remembers the trackers by paramURL/liveURI/storedURI to avoid duplication
|
||||||
|
*/
|
||||||
|
private final Map<Triple<URL, URI, URI>, RaceTracker> raceTrackersByURLs;
|
||||||
|
|
||||||
private final Map<Integer, UDPExpeditionReceiver> windReceivers;
|
private final Map<Integer, UDPExpeditionReceiver> windReceivers;
|
||||||
|
|
||||||
public RacingEventServiceImpl() {
|
public RacingEventServiceImpl() {
|
||||||
domainFactory = DomainFactory.INSTANCE;
|
domainFactory = DomainFactory.INSTANCE;
|
||||||
eventsByName = new HashMap<String, Event>();
|
eventsByName = new HashMap<String, Event>();
|
||||||
raceTrackers = new HashMap<Event, Set<RaceTracker>>();
|
raceTrackersByEvent = new HashMap<Event, Set<RaceTracker>>();
|
||||||
windTrackers = new HashMap<RaceDefinition, Pair<WindTracker, Integer>>();
|
windTrackers = new HashMap<RaceDefinition, Pair<WindTracker, Integer>>();
|
||||||
|
raceTrackersByURLs = new HashMap<Triple<URL, URI, URI>, RaceTracker>();
|
||||||
windReceivers = new HashMap<Integer, UDPExpeditionReceiver>();
|
windReceivers = new HashMap<Integer, UDPExpeditionReceiver>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,22 +81,28 @@ public class RacingEventServiceImpl implements RacingEventService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore) throws URISyntaxException, IOException, ParseException, org.json.simple.parser.ParseException {
|
public Event addEvent(URL jsonURL, URI liveURI, URI storedURI, WindStore windStore) throws URISyntaxException, IOException, ParseException, org.json.simple.parser.ParseException {
|
||||||
JSONService jsonService = getDomainFactory().parseJSONURL(jsonURL);
|
JSONService jsonService = getDomainFactory().parseJSONURL(jsonURL);
|
||||||
|
Event event = null;
|
||||||
for (RaceRecord rr : jsonService.getRaceRecords()) {
|
for (RaceRecord rr : jsonService.getRaceRecords()) {
|
||||||
URL paramURL = rr.getParamURL();
|
URL paramURL = rr.getParamURL();
|
||||||
addRace(paramURL, liveURI, storedURI, windStore);
|
event = addRace(paramURL, liveURI, storedURI, windStore).getEvent();
|
||||||
}
|
}
|
||||||
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException,
|
public RaceHandle addRace(URL paramURL, URI liveURI, URI storedURI, WindStore windStore) throws MalformedURLException, FileNotFoundException,
|
||||||
URISyntaxException {
|
URISyntaxException {
|
||||||
RaceTracker tracker = getDomainFactory().createRaceTracker(paramURL, liveURI, storedURI, windStore);
|
Triple<URL, URI, URI> key = new Triple<URL, URI, URI>(paramURL, liveURI, storedURI);
|
||||||
Set<RaceTracker> trackers = raceTrackers.get(tracker.getEvent());
|
RaceTracker tracker = raceTrackersByURLs.get(key);
|
||||||
|
if (tracker == null) {
|
||||||
|
tracker = getDomainFactory().createRaceTracker(paramURL, liveURI, storedURI, windStore);
|
||||||
|
raceTrackersByURLs.put(key, tracker);
|
||||||
|
Set<RaceTracker> trackers = raceTrackersByEvent.get(tracker.getEvent());
|
||||||
if (trackers == null) {
|
if (trackers == null) {
|
||||||
trackers = new HashSet<RaceTracker>();
|
trackers = new HashSet<RaceTracker>();
|
||||||
raceTrackers.put(tracker.getEvent(), trackers);
|
raceTrackersByEvent.put(tracker.getEvent(), trackers);
|
||||||
}
|
}
|
||||||
trackers.add(tracker);
|
trackers.add(tracker);
|
||||||
String eventName = tracker.getEvent().getName();
|
String eventName = tracker.getEvent().getName();
|
||||||
@@ -98,15 +114,24 @@ public class RacingEventServiceImpl implements RacingEventService {
|
|||||||
} else {
|
} else {
|
||||||
eventsByName.put(eventName, tracker.getEvent());
|
eventsByName.put(eventName, tracker.getEvent());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
WindStore existingTrackersWindStore = tracker.getWindStore();
|
||||||
|
if (!existingTrackersWindStore.equals(windStore)) {
|
||||||
|
logger.warning("Wind store mismatch. Requested wind store: "+windStore+
|
||||||
|
". Wind store in use by existing tracker: "+existingTrackersWindStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tracker.getRaceHandle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopTracking(Event event) throws MalformedURLException, IOException, InterruptedException {
|
public void stopTracking(Event event) throws MalformedURLException, IOException, InterruptedException {
|
||||||
if (raceTrackers.containsKey(event)) {
|
if (raceTrackersByEvent.containsKey(event)) {
|
||||||
for (RaceTracker raceTracker : raceTrackers.get(event)) {
|
for (RaceTracker raceTracker : raceTrackersByEvent.get(event)) {
|
||||||
raceTracker.stop();
|
raceTracker.stop(); // this also removes the TrackedRace from trackedEvent
|
||||||
|
raceTrackersByURLs.remove(raceTracker.getURLs());
|
||||||
}
|
}
|
||||||
raceTrackers.remove(event);
|
raceTrackersByEvent.remove(event);
|
||||||
}
|
}
|
||||||
if (event != null && event.getName() != null) {
|
if (event != null && event.getName() != null) {
|
||||||
eventsByName.remove(event.getName());
|
eventsByName.remove(event.getName());
|
||||||
@@ -118,18 +143,19 @@ public class RacingEventServiceImpl implements RacingEventService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopTracking(Event event, RaceDefinition race) throws MalformedURLException, IOException, InterruptedException {
|
public void stopTracking(Event event, RaceDefinition race) throws MalformedURLException, IOException, InterruptedException {
|
||||||
if (raceTrackers.containsKey(event)) {
|
if (raceTrackersByEvent.containsKey(event)) {
|
||||||
Iterator<RaceTracker> trackerIter = raceTrackers.get(event).iterator();
|
Iterator<RaceTracker> trackerIter = raceTrackersByEvent.get(event).iterator();
|
||||||
while (trackerIter.hasNext()) {
|
while (trackerIter.hasNext()) {
|
||||||
RaceTracker raceTracker = trackerIter.next();
|
RaceTracker raceTracker = trackerIter.next();
|
||||||
if (raceTracker.getRace() == race) {
|
if (raceTracker.getRace() == race) {
|
||||||
raceTracker.stop();
|
raceTracker.stop(); // this also removes the TrackedRace from trackedEvent
|
||||||
trackerIter.remove();
|
trackerIter.remove();
|
||||||
|
raceTrackersByURLs.remove(raceTracker.getURLs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if the last tracked race was removed, remove the entire event
|
// if the last tracked race was removed, remove the entire event
|
||||||
if (raceTrackers.get(event).isEmpty()) {
|
if (raceTrackersByEvent.get(event).isEmpty()) {
|
||||||
stopTracking(event);
|
stopTracking(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +164,7 @@ public class RacingEventServiceImpl implements RacingEventService {
|
|||||||
public synchronized void startTrackingWind(Event event, RaceDefinition race, int port,
|
public synchronized void startTrackingWind(Event event, RaceDefinition race, int port,
|
||||||
DeclinationService declinationService) throws SocketException {
|
DeclinationService declinationService) throws SocketException {
|
||||||
if (!windTrackers.containsKey(race)) {
|
if (!windTrackers.containsKey(race)) {
|
||||||
DynamicTrackedEvent trackedEvent = getDomainFactory().trackEvent(event);
|
DynamicTrackedEvent trackedEvent = getDomainFactory().getOrCreateTrackedEvent(event);
|
||||||
DynamicTrackedRace trackedRace = trackedEvent.getTrackedRace(race);
|
DynamicTrackedRace trackedRace = trackedEvent.getTrackedRace(race);
|
||||||
WindTracker windTracker = new WindTracker(trackedRace, declinationService);
|
WindTracker windTracker = new WindTracker(trackedRace, declinationService);
|
||||||
UDPExpeditionReceiver receiver = getOrCreateWindReceiverForPort(port);
|
UDPExpeditionReceiver receiver = getOrCreateWindReceiverForPort(port);
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ def liveRaceInformation(configurator):
|
|||||||
# check for cases where calculations from backend yield
|
# check for cases where calculations from backend yield
|
||||||
# strange numbers - this happens in cases where the competitor
|
# strange numbers - this happens in cases where the competitor
|
||||||
# has no speed during mark passing
|
# has no speed during mark passing
|
||||||
if k in ['Infinity', 'Infinite']:
|
if k in ['Infinity', 'Infinite'] or float(k) > 150000.0:
|
||||||
|
|
||||||
# use a magic number (that is very unlikely to occur)
|
# use a magic number (that is very unlikely to occur)
|
||||||
# to indicate that competitor has no speed
|
# to indicate that competitor has no speed
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class Competitor(BaseDocument):
|
|||||||
|
|
||||||
'leg_indexes': {}, # holds association between leg position and unique id <position in marks>:<unique id>
|
'leg_indexes': {}, # holds association between leg position and unique id <position in marks>:<unique id>
|
||||||
|
|
||||||
'total': float,
|
'total': int, # total rank
|
||||||
'current_rank': int, # current rank for given time
|
'current_rank': int, # current rank for given time
|
||||||
'races' : [float, ],
|
'races' : [float, ],
|
||||||
'marks' : [[float, ], ], # ranks for marks
|
'marks' : [[float, ], ], # ranks for marks
|
||||||
|
|||||||
@@ -501,7 +501,7 @@ def adminLiveData(context, request):
|
|||||||
|
|
||||||
results += '\nLAST SHOWRACE CALL: %s (UPCOUNT PARAM: %s)\nLEG: %s (FROM: %s TO: %s)\nRACE-START: %s NEWEST EVENT: %s WIND: (%s %s %s)\n' % (t_up, t_upcount, legpos+1, competitors[0].marknames[racepos][legpos][0], competitors[0].marknames[racepos][legpos][1], view.millisToDatetime(race.start), view.millisToDatetime(race.timeofnewestevent), race.wind_source, race.wind_bearing, race.wind_speed)
|
results += '\nLAST SHOWRACE CALL: %s (UPCOUNT PARAM: %s)\nLEG: %s (FROM: %s TO: %s)\nRACE-START: %s NEWEST EVENT: %s WIND: (%s %s %s)\n' % (t_up, t_upcount, legpos+1, competitors[0].marknames[racepos][legpos][0], competitors[0].marknames[racepos][legpos][1], view.millisToDatetime(race.start), view.millisToDatetime(race.timeofnewestevent), race.wind_source, race.wind_bearing, race.wind_speed)
|
||||||
results += 'NAME'.ljust(16) + 'TOTAL'.ljust(7) + 'CRANK'.ljust(7) + 'RRANK'.ljust(7)
|
results += 'NAME'.ljust(16) + 'TOTAL'.ljust(7) + 'CRANK'.ljust(7) + 'RRANK'.ljust(7)
|
||||||
results += 'MRANK'.ljust(7) + 'LRANK'.ljust(9) + 'SPD'.ljust(9) + 'DSTTRV'.ljust(9) + 'VMG'.ljust(9) + 'AVMG'.ljust(9) + 'SGAP'.ljust(9) + 'ETA'.ljust(9) + 'DSTGO'.ljust(9) + 'FINISHD'.ljust(9) + 'STARTD'.ljust(9) + 'UPDOWNWIND'.ljust(12)
|
results += 'MRANK'.ljust(7) + 'LRANK'.ljust(9) + 'SPD'.ljust(9) + 'DSTTRV'.ljust(9) + 'VMG'.ljust(9) + 'AVMG'.ljust(9) + 'SGAP'.ljust(9) + 'ETA'.ljust(9) + 'DSTGO'.ljust(9) + 'STARTD'.ljust(9) + 'FINISHD'.ljust(9) + 'UPDOWNWIND'.ljust(12)
|
||||||
results += '\n'
|
results += '\n'
|
||||||
|
|
||||||
# sort competitors by rank in current leg
|
# sort competitors by rank in current leg
|
||||||
@@ -533,3 +533,69 @@ def adminLiveData(context, request):
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
@jsonize
|
||||||
|
def moderatorLiveData(context, request):
|
||||||
|
""" Returns data for the moderators leaderboard """
|
||||||
|
|
||||||
|
view = core.BaseView(context, request)
|
||||||
|
event = view.currentLeaderboardEvent()
|
||||||
|
|
||||||
|
sortby = request.params.get('sortby', 'name')
|
||||||
|
race_range = request.params.get('races', '1:3')
|
||||||
|
competitor_range = request.params.get('competitors', '1:20')
|
||||||
|
direction = request.params.get('direction', 'asc');
|
||||||
|
|
||||||
|
if race_range in ['null', 'undefined']:
|
||||||
|
race_range = '1:3'
|
||||||
|
|
||||||
|
race_start_index, race_end_index = race_range.split(':')
|
||||||
|
races_list = event.races[int(race_start_index)-1:int(race_end_index)]
|
||||||
|
|
||||||
|
competitors = view.competitorsSortedBy(event.name, sortby.strip())
|
||||||
|
|
||||||
|
if direction == 'asc':
|
||||||
|
competitors.reverse()
|
||||||
|
|
||||||
|
races = []
|
||||||
|
for racename in races_list:
|
||||||
|
races.append(model.RaceImpl.queryOneBy(name=racename, event=event.name))
|
||||||
|
|
||||||
|
# list of competitors with corresponding data
|
||||||
|
data = []
|
||||||
|
|
||||||
|
for competitor in competitors:
|
||||||
|
racedata = competitor.races[int(race_start_index)-1:int(race_end_index)]
|
||||||
|
|
||||||
|
# for each race found compute the marks and values
|
||||||
|
markranks = []; legvalues = []; racecounter = 0
|
||||||
|
for racename in races_list:
|
||||||
|
real_racepos = event.races.index(racename)
|
||||||
|
if len(markranks) < racecounter+1:
|
||||||
|
markranks.append([])
|
||||||
|
legvalues.append([])
|
||||||
|
|
||||||
|
markranks[racecounter] = markranks[racecounter] + competitor.marks[real_racepos]
|
||||||
|
|
||||||
|
iv = competitor.values[real_racepos]
|
||||||
|
|
||||||
|
newvalues = iv
|
||||||
|
for valpos in range(len(iv)):
|
||||||
|
nv = ['%.2f' % (val < 15000 and val or 0.0) for val in newvalues[valpos]]
|
||||||
|
|
||||||
|
# prepare values according to UI
|
||||||
|
# UI: RANK, DSTTRAV, SPEED, VMG, SGAP, DSTGO, ETA
|
||||||
|
# INTERN: RANK, AVGSPEED, DSTRV, VMG, AVMG, SGAP, ETA, DSTGO
|
||||||
|
if nv:
|
||||||
|
nv = [nv[0], nv[2], nv[1], nv[3], nv[5], nv[-1], nv[-2]]
|
||||||
|
newvalues[valpos] = nv
|
||||||
|
|
||||||
|
legvalues[racecounter] = legvalues[racecounter] + newvalues
|
||||||
|
|
||||||
|
racecounter += 1
|
||||||
|
|
||||||
|
data.append({'name': competitor.name, 'raceranks': racedata, 'markranks': markranks, 'legvalues': legvalues,
|
||||||
|
'nationality': competitor.nationality, 'global_rank': competitor.total})
|
||||||
|
|
||||||
|
c_range_start, c_range_end = competitor_range.split(':')
|
||||||
|
return data[int(c_range_start)-1:int(c_range_end)]
|
||||||
|
|
||||||
@@ -1,6 +1,154 @@
|
|||||||
|
|
||||||
|
var global_sortkey = 'name';
|
||||||
|
var global_direction = 'desc';
|
||||||
|
var global_race = '1:3';
|
||||||
|
var global_competitors = '1:20';
|
||||||
|
|
||||||
|
var loader_image = "<img style='margin: 3px 0px 3px 3px' src='/static/images/ajax-loader-greybg.gif'/>";
|
||||||
|
|
||||||
|
var listener_paused = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Loads leaderboard data. Parameters:
|
||||||
|
*
|
||||||
|
* races: Slice that indicates which data to load (e.g. "1:3")
|
||||||
|
* sortby: Sorting parameters (e.g "1,1,1")
|
||||||
|
* competitors: Slice indicating which competitors to show (e.g. "1:20")
|
||||||
|
*/
|
||||||
|
function loadLeaderboard(races, sortby, competitors, direction) {
|
||||||
|
showLoader();
|
||||||
|
$.getJSON('/++/moderatorLiveData',
|
||||||
|
{races:races, sortby:sortby, competitors:competitors, direction:direction},
|
||||||
|
function(data) {
|
||||||
|
displayLeaderboard(data);
|
||||||
|
|
||||||
|
global_race = races;
|
||||||
|
global_sortkey = sortby;
|
||||||
|
global_competitors = competitors;
|
||||||
|
global_direction = direction;
|
||||||
|
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function liveRefresh() {
|
||||||
|
if (listener_paused == false)
|
||||||
|
loadLeaderboard(global_race, global_sortkey, global_competitors, global_direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleListener() {
|
||||||
|
$('#refresh-button').toggleClass('refresh');
|
||||||
|
if ($('#refresh-button').hasClass('refresh')) {
|
||||||
|
$('#refresh-button').css('background-image', 'url(/moderator-static/refresh-icon.png)');
|
||||||
|
listener_paused = true;
|
||||||
|
} else {
|
||||||
|
listener_paused = false;
|
||||||
|
$('#refresh-button').css('background-image', 'url(/moderator-static/pause_button.png)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoader() {
|
||||||
|
$('.refresh-btn').html(loader_image);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoader() {
|
||||||
|
$('.refresh-btn').html('');
|
||||||
|
}
|
||||||
|
function sortBy(param, element) {
|
||||||
|
element.toggleClass('sort-asc').toggleClass('sort-desc');
|
||||||
|
|
||||||
|
if (element.hasClass('sort-asc'))
|
||||||
|
global_direction = 'asc';
|
||||||
|
else global_direction = 'desc';
|
||||||
|
|
||||||
|
global_sortkey = param;
|
||||||
|
loadLeaderboard(global_race, param, global_competitors, global_direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
function yieldValue(element, newvalue) {
|
||||||
|
element.html(newvalue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts data into the right context for the leaderboard
|
||||||
|
*/
|
||||||
|
function displayLeaderboard(data) {
|
||||||
|
rowid = 1;
|
||||||
|
for (cpos in data) {
|
||||||
|
competitor = data[cpos];
|
||||||
|
|
||||||
|
/* always change global rank */
|
||||||
|
$('#clipping-'+rowid+'-1 span').html(competitor.global_rank);
|
||||||
|
|
||||||
|
name_element = $('#clipping-'+rowid+'-3 span');
|
||||||
|
if (name_element.html() != competitor.name) {
|
||||||
|
/* competitor position has changed - refresh whole line */
|
||||||
|
|
||||||
|
name_element.html(competitor.name);
|
||||||
|
$('#clipping-'+rowid+'-2 span').html(competitor.nationality);
|
||||||
|
|
||||||
|
/* now set values independent what has been there before */
|
||||||
|
racepos = 1;
|
||||||
|
for (racerank in competitor.raceranks) {
|
||||||
|
$('#race-'+racepos+'-rankrow-'+rowid).html(competitor.raceranks[racerank]);
|
||||||
|
|
||||||
|
markpos = 1;
|
||||||
|
for (markrank in competitor.markranks[racepos-1]) {
|
||||||
|
$('#race-'+racepos+'-mark-'+markpos+'-row-'+rowid).html(competitor.markranks[racepos-1][markrank]);
|
||||||
|
|
||||||
|
legpos = 1;
|
||||||
|
for (legvalue in competitor.legvalues[racepos-1][markpos-1]) {
|
||||||
|
if (legvalue == 0) {
|
||||||
|
$('#race-'+racepos+'-mark-'+markpos+'-legrow-'+rowid).html(parseFloat(competitor.legvalues[racepos-1][markpos-1][legvalue]).toFixed());
|
||||||
|
} else {
|
||||||
|
$('#race-'+racepos+'-mark-'+markpos+'-valrow-'+rowid+'-pos-'+(legpos-1)+' span').html(competitor.legvalues[racepos-1][markpos-1][legvalue]);
|
||||||
|
}
|
||||||
|
legpos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
markpos += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* check if values have changed */
|
||||||
|
name_element.html(competitor.name);
|
||||||
|
$('#clipping-'+rowid+'-2 span').html(competitor.nationality);
|
||||||
|
|
||||||
|
for (racerank in competitor.raceranks) {
|
||||||
|
yieldValue($('#race-'+racepos+'-rankrow-'+rowid), competitor.raceranks[racerank]);
|
||||||
|
|
||||||
|
markpos = 1;
|
||||||
|
for (markrank in competitor.markranks[racepos-1]) {
|
||||||
|
yieldValue($('#race-'+racepos+'-mark-'+markpos+'-row-'+rowid), competitor.markranks[racepos-1][markrank]);
|
||||||
|
|
||||||
|
legpos = 1;
|
||||||
|
for (legvalue in competitor.legvalues[racepos-1][markpos-1]) {
|
||||||
|
if (legvalue == 0) {
|
||||||
|
yieldValue($('#race-'+racepos+'-mark-'+markpos+'-legrow-'+rowid), parseFloat(competitor.legvalues[racepos-1][markpos-1][legvalue]).toFixed());
|
||||||
|
} else {
|
||||||
|
yieldValue($('#race-'+racepos+'-mark-'+markpos+'-valrow-'+rowid+'-pos-'+(legpos-1)+' span'), competitor.legvalues[racepos-1][markpos-1][legvalue]);
|
||||||
|
}
|
||||||
|
legpos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
markpos += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rowid += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var wHeight = $(window).height();
|
var wHeight = $(window).height();
|
||||||
|
|
||||||
$("#rootwrapper").css("height", wHeight);
|
$("#rootwrapper").css("height", wHeight);
|
||||||
$("#appinterface").css("height", wHeight-131);
|
$("#appinterface").css("height", wHeight-131);
|
||||||
|
|
||||||
|
$.ajaxSetup({cache:false});
|
||||||
|
|
||||||
|
loadLeaderboard(global_race, global_sortkey, global_competitors, global_direction);
|
||||||
|
window.setInterval('liveRefresh()', 5000);
|
||||||
});
|
});
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 165 B |
@@ -122,7 +122,7 @@ body {
|
|||||||
padding: 10px 0 10px 30px;
|
padding: 10px 0 10px 30px;
|
||||||
border-top: 1px solid #e1e1e1; }
|
border-top: 1px solid #e1e1e1; }
|
||||||
#header ul.systeminfo li.active {
|
#header ul.systeminfo li.active {
|
||||||
background: url(/moderator-static/refresh-icon.png) no-repeat left center; }
|
background: url(/moderator-static/pause_button.png) no-repeat left center; }
|
||||||
#header ul.systeminfo li.message-0 {
|
#header ul.systeminfo li.message-0 {
|
||||||
border: none; }
|
border: none; }
|
||||||
#header ul.systeminfo:after {
|
#header ul.systeminfo:after {
|
||||||
@@ -221,7 +221,7 @@ span.race-count {
|
|||||||
#main-interface .blue-table .refresh-btn {
|
#main-interface .blue-table .refresh-btn {
|
||||||
display: block;
|
display: block;
|
||||||
height: 39px;
|
height: 39px;
|
||||||
background: url(/moderator-static/refresh-icon.png) right center no-repeat #484848; }
|
background: url(/moderator-static/pause_button.png) right center no-repeat #484848; }
|
||||||
#main-interface .blue-table .sort-rank {
|
#main-interface .blue-table .sort-rank {
|
||||||
display: block;
|
display: block;
|
||||||
height: 39px; }
|
height: 39px; }
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user