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
|
||||
====
|
||||
|
||||
- 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
|
||||
|
||||
@@ -10,10 +9,6 @@ Axel
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
|
||||
|
||||
@@ -30,8 +29,6 @@ Simon
|
||||
|
||||
+ Server: Mehr RAM bestellen
|
||||
+ Prüfen, warum in der Map Anzeige manchmal ein Competitor doppelt ist
|
||||
+ Simulator mit Speed = 1 anschmeissen und dann prüfen was passiert
|
||||
+ 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
|
||||
+ ANCHOR anzeigen für große Werte (Bei DSTTRV raussuchen was da z.B. groß ist)
|
||||
+ Implementierung Leaderboard auf Basis HTML Designer
|
||||
+ 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 {
|
||||
Declination declination = importer.getDeclination(new DegreePosition(53, 3),
|
||||
new MillisecondsTimePoint(simpleDateFormat.parse("2011-05-27").getTime()),
|
||||
/* timeoutForOnlineFetchInMilliseconds */ 5000);
|
||||
/* timeoutForOnlineFetchInMilliseconds */ 10000);
|
||||
assertNotNull(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();
|
||||
domainFactory = new DomainFactoryImpl();
|
||||
domainEvent = domainFactory.createEvent(getEvent());
|
||||
trackedEvent = domainFactory.trackEvent(domainEvent);
|
||||
trackedEvent = domainFactory.getOrCreateTrackedEvent(domainEvent);
|
||||
receivers.add(new RaceCourseReceiver(domainFactory, trackedEvent, getEvent(), /* millisecondsOverWhichToAverageWind */
|
||||
EmptyWindStore.INSTANCE,
|
||||
30000, /* millisecondsOverWhichToAverageSpeed */30000) {
|
||||
@@ -81,7 +81,10 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
|
||||
}
|
||||
});
|
||||
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();
|
||||
assertNotNull(course);
|
||||
assertEquals(3, Util.size(course.getWaypoints()));
|
||||
@@ -91,6 +94,11 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest {
|
||||
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
|
||||
* {@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>();
|
||||
receivers.add(receiver);
|
||||
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,
|
||||
ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS)) {
|
||||
receivers.add(r);
|
||||
|
||||
+4
-1
@@ -72,13 +72,16 @@ public class ReceiveTrackingDataTest extends AbstractTracTracLiveTest {
|
||||
};
|
||||
List<TypeController> listeners = new ArrayList<TypeController>();
|
||||
Event event = domainFactory.createEvent(getEvent());
|
||||
DynamicTrackedEvent trackedEvent = domainFactory.trackEvent(event);
|
||||
DynamicTrackedEvent trackedEvent = domainFactory.getOrCreateTrackedEvent(event);
|
||||
trackedEvent.addRaceListener(new RaceListener() {
|
||||
@Override
|
||||
public void raceAdded(TrackedRace trackedRace) {
|
||||
System.out.println("Subscribing raw position listener for race "+trackedRace);
|
||||
((DynamicTrackedRace) trackedRace).addListener(positionListener);
|
||||
}
|
||||
@Override
|
||||
public void raceRemoved(TrackedRace trackedRace) {
|
||||
}
|
||||
});
|
||||
for (Receiver receiver : domainFactory.getUpdateReceivers(trackedEvent, getEvent(), EmptyWindStore.INSTANCE)) {
|
||||
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,
|
||||
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);
|
||||
|
||||
@@ -93,7 +97,7 @@ public interface DomainFactory {
|
||||
*
|
||||
* @param trackedEvent
|
||||
* 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
|
||||
* @param windStore TODO
|
||||
*/
|
||||
@@ -117,6 +121,11 @@ public interface DomainFactory {
|
||||
DynamicTrackedRace trackRace(TrackedEvent trackedEvent, RaceDefinition raceDefinition,
|
||||
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);
|
||||
|
||||
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.
|
||||
*/
|
||||
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.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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();
|
||||
|
||||
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 {
|
||||
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
|
||||
private final long millisecondsOverWhichToAverageWind = 30000;
|
||||
@@ -340,7 +340,12 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
}
|
||||
|
||||
@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) {
|
||||
DynamicTrackedEvent result = eventTrackingCache.get(event);
|
||||
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.
|
||||
try {
|
||||
getDomainFactory().updateCourseWaypoints(course, event.getB().getPoints());
|
||||
if (trackedEvent.getExistingTrackedRace(existingRaceDefinitionForRace) == null) {
|
||||
createTrackedRace(existingRaceDefinitionForRace);
|
||||
}
|
||||
} catch (PatchFailedException e) {
|
||||
logger.log(Level.SEVERE, "Internal error updating race course "+course+": "+e.getMessage());
|
||||
logger.throwing(RaceCourseReceiver.class.getName(), "handleEvent", e);
|
||||
}
|
||||
} else {
|
||||
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);
|
||||
trackedEvent.getEvent().addRace(raceDefinition);
|
||||
DynamicTrackedRace trackedRace = getDomainFactory().trackRace(trackedEvent, raceDefinition,
|
||||
windStore, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, tractracEvent);
|
||||
trackedEvent.addTrackedRace(trackedRace);
|
||||
createTrackedRace(raceDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
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.TrackedRace;
|
||||
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.tractracadapter.DomainFactory;
|
||||
import com.sap.sailing.domain.tractracadapter.RaceHandle;
|
||||
import com.sap.sailing.domain.tractracadapter.RaceTracker;
|
||||
import com.sap.sailing.domain.tractracadapter.Receiver;
|
||||
import com.sap.sailing.util.Util.Triple;
|
||||
import com.tractrac.clientmodule.Event;
|
||||
import com.tractrac.clientmodule.data.DataController;
|
||||
import com.tractrac.clientmodule.data.DataController.Listener;
|
||||
@@ -31,10 +34,18 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
|
||||
private final Thread ioThread;
|
||||
private final DataController controller;
|
||||
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
|
||||
* 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
|
||||
* {@link com.sap.sailing.domain.base.Event}.
|
||||
* <p>
|
||||
@@ -45,16 +56,16 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
|
||||
* {@link Event} as argument that is used for its tracking.
|
||||
* <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
|
||||
* Provides the capability to obtain the {@link WindTrack}s for the different wind sources.
|
||||
* A trivial implementation is {@link EmptyWindStore} which simply provides new, empty tracks.
|
||||
* This is always available but loses track of the wind, e.g., during server restarts.
|
||||
* Provides the capability to obtain the {@link WindTrack}s for the different wind sources. A trivial
|
||||
* implementation is {@link EmptyWindStore} which simply provides new, empty tracks. This is always
|
||||
* available but loses track of the wind, e.g., during server restarts.
|
||||
*/
|
||||
protected RaceTrackerImpl(DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI, WindStore windStore)
|
||||
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
|
||||
tractracEvent = KeyValue.setup(paramURL);
|
||||
|
||||
@@ -69,7 +80,7 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
|
||||
// Start live and stored data streams
|
||||
ioThread = new Thread(controller, "io");
|
||||
domainEvent = domainFactory.createEvent(tractracEvent);
|
||||
DynamicTrackedEvent trackedEvent = domainFactory.trackEvent(domainEvent);
|
||||
trackedEvent = domainFactory.getOrCreateTrackedEvent(domainEvent);
|
||||
receivers = new HashSet<Receiver>();
|
||||
Set<TypeController> typeControllers = new HashSet<TypeController>();
|
||||
for (Receiver receiver : domainFactory.getUpdateReceivers(trackedEvent, tractracEvent, windStore)) {
|
||||
@@ -81,9 +92,29 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
|
||||
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
|
||||
public RaceDefinition getRace() {
|
||||
return DomainFactory.INSTANCE.getRace(tractracEvent);
|
||||
return domainFactory.getRace(tractracEvent);
|
||||
}
|
||||
|
||||
protected void addListenersForStoredDataAndStartController(Iterable<TypeController> listenersForStoredData) {
|
||||
@@ -118,7 +149,8 @@ public class RaceTrackerImpl implements Listener, RaceTracker {
|
||||
for (Receiver receiver : receivers) {
|
||||
receiver.stop();
|
||||
}
|
||||
ioThread.join();
|
||||
ioThread.join(3000); // wait no more than three seconds
|
||||
trackedEvent.removedTrackedRace(trackedEvent.getTrackedRace(getRace()));
|
||||
}
|
||||
|
||||
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.Position;
|
||||
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.impl.KilometersPerHourSpeedImpl;
|
||||
import com.sap.sailing.domain.base.impl.KnotSpeedImpl;
|
||||
@@ -139,4 +140,13 @@ public class TracTracTrackedLegOfCompetitor implements TrackedLegOfCompetitor {
|
||||
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 {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return (o instanceof Distance) && this.compareTo((Distance) o) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 9128347 ^ (int) getMeters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Distance o) {
|
||||
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;
|
||||
|
||||
public interface DynamicTrackedEvent extends TrackedEvent {
|
||||
|
||||
DynamicTrackedRace getTrackedRace(RaceDefinition race);
|
||||
|
||||
}
|
||||
@@ -2,4 +2,6 @@ package com.sap.sailing.domain.tracking;
|
||||
|
||||
public interface RaceListener {
|
||||
void raceAdded(TrackedRace trackedRace);
|
||||
|
||||
void raceRemoved(TrackedRace trackedRace);
|
||||
}
|
||||
@@ -19,8 +19,16 @@ public interface TrackedEvent {
|
||||
*/
|
||||
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 removedTrackedRace(TrackedRace trackedRace);
|
||||
|
||||
/**
|
||||
* Listener will be notified when {@link #addTrackedRace(TrackedRace)} is called and
|
||||
* 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.Leg;
|
||||
import com.sap.sailing.domain.base.Speed;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.base.TimePoint;
|
||||
|
||||
public interface TrackedLegOfCompetitor {
|
||||
@@ -91,4 +92,6 @@ public interface TrackedLegOfCompetitor {
|
||||
*/
|
||||
Double getEstimatedTimeToNextMarkInSeconds(TimePoint timePoint) throws NoWindException;
|
||||
|
||||
SpeedWithBearing getSpeedOverGround(TimePoint at);
|
||||
|
||||
}
|
||||
@@ -137,8 +137,7 @@ public interface TrackedRace {
|
||||
WindTrack getWindTrack(WindSource windSource);
|
||||
|
||||
/**
|
||||
* Waits until {@link #getUpdateCount()} is after <code>since</code>.
|
||||
* @param sinceUpdate TODO
|
||||
* Waits until {@link #getUpdateCount()} is after <code>sinceUpdate</code>.
|
||||
*/
|
||||
void waitForNextUpdate(int sinceUpdate) throws InterruptedException;
|
||||
|
||||
@@ -149,4 +148,10 @@ public interface TrackedRace {
|
||||
NavigableSet<MarkPassing> getMarkPassings(Competitor competitor);
|
||||
|
||||
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);
|
||||
getMarkPassingsInOrder(markPassing.getWaypoint()).add(markPassing);
|
||||
updated(markPassing.getTimePoint());
|
||||
}
|
||||
// notify *after* all mark passings have been re-established; should avoid flicker
|
||||
for (MarkPassing markPassing : markPassings) {
|
||||
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
|
||||
public Event getEvent() {
|
||||
return event;
|
||||
@@ -80,6 +98,11 @@ public class TrackedEventImpl implements TrackedEvent {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackedRace getExistingTrackedRace(RaceDefinition race) {
|
||||
return trackedRaces.get(race);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRaceListener(RaceListener listener) {
|
||||
raceListeners.add(listener);
|
||||
|
||||
+9
-6
@@ -77,7 +77,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
if (legStart == null) {
|
||||
return Distance.NULL;
|
||||
} else {
|
||||
MarkPassing legEnd = getTrackedRace().getMarkPassing(getCompetitor(), getLeg().getTo());
|
||||
MarkPassing legEnd = getMarkPassingForLegEnd();
|
||||
TimePoint end = timePoint;
|
||||
if (legEnd != null && timePoint.compareTo(legEnd.getTimePoint()) > 0) {
|
||||
// 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 {
|
||||
Speed result = null;
|
||||
MarkPassing start = getMarkPassingForLegStart();
|
||||
if (start != null && start.getTimePoint().compareTo(timePoint) > 0) {
|
||||
if (start != null && start.getTimePoint().compareTo(timePoint) <= 0) {
|
||||
MarkPassing end = getMarkPassingForLegEnd();
|
||||
if (end != null) {
|
||||
TimePoint to;
|
||||
@@ -254,13 +254,15 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
} else {
|
||||
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(
|
||||
getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(start.getTimePoint(), false), endPos,
|
||||
to);
|
||||
getTrackedRace().getTrack(getCompetitor())
|
||||
.getEstimatedPosition(start.getTimePoint(), false), endPos, to);
|
||||
result = d.inTime(to.asMillis() - start.getTimePoint().asMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(at);
|
||||
} else {
|
||||
|
||||
+7
@@ -46,6 +46,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
private TimePoint start;
|
||||
private TimePoint finish;
|
||||
private TimePoint timePointOfNewestEvent;
|
||||
private TimePoint timePointOfLastEvent;
|
||||
private int updateCount;
|
||||
|
||||
private final Map<TimePoint, TreeSet<Competitor>> competitorRankings;
|
||||
@@ -353,6 +354,11 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
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
|
||||
*/
|
||||
@@ -366,6 +372,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
if (startOfTracking == null || startOfTracking.compareTo(timeOfEvent) > 0) {
|
||||
startOfTracking = timeOfEvent;
|
||||
}
|
||||
timePointOfLastEvent = timeOfEvent;
|
||||
}
|
||||
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(13, cal.get(Calendar.HOUR_OF_DAY));
|
||||
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
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ public class TestStoringAndRetrievingWindTracks extends AbstractTracTracLiveTest
|
||||
public void testStoreAFewWindEntries() throws UnknownHostException, MongoException, InterruptedException {
|
||||
DomainFactory domainFactory = DomainFactory.INSTANCE;
|
||||
Event domainEvent = domainFactory.createEvent(getEvent());
|
||||
DynamicTrackedEvent trackedEvent = domainFactory.trackEvent(domainEvent);
|
||||
DynamicTrackedEvent trackedEvent = domainFactory.getOrCreateTrackedEvent(domainEvent);
|
||||
Iterable<Receiver> typeControllers = domainFactory.getUpdateReceivers(trackedEvent, getEvent(),
|
||||
EmptyWindStore.INSTANCE, ReceiverType.RACECOURSE);
|
||||
addListenersForStoredDataAndStartController(typeControllers);
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ public class MongoWindStoreFactoryImpl implements MongoWindStoreFactory, BundleA
|
||||
}
|
||||
defaultHostName = context.getProperty(MONGO_HOSTNAME);
|
||||
if (defaultHostName == null) {
|
||||
defaultHostName = "localhost";
|
||||
defaultHostName = "127.0.0.1";
|
||||
} else {
|
||||
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>
|
||||
<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.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"/>
|
||||
<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"/>
|
||||
|
||||
@@ -160,7 +160,7 @@ public class AdminApp extends Servlet {
|
||||
if (race == null) {
|
||||
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
|
||||
} 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 oneHourLater = new MillisecondsTimePoint(time.asMillis()+3600*1000);
|
||||
String[] latitudes = req.getParameterValues(PARAM_NAME_LATDEG);
|
||||
@@ -202,7 +202,7 @@ public class AdminApp extends Servlet {
|
||||
if (race == null) {
|
||||
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
|
||||
} 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,
|
||||
trackedRace.getStart()==null?new MillisecondsTimePoint(0):
|
||||
/* 24h before race start */ new MillisecondsTimePoint(trackedRace.getStart().asMillis()-24*3600*1000));
|
||||
@@ -257,7 +257,7 @@ public class AdminApp extends Servlet {
|
||||
if (race == null) {
|
||||
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found");
|
||||
} else {
|
||||
TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event)
|
||||
TrackedRace trackedRace = getService().getDomainFactory().getTrackedEvent(event)
|
||||
.getTrackedRace(race);
|
||||
trackedRace.setWindSource(windSource);
|
||||
resp.getWriter().println(
|
||||
@@ -313,7 +313,7 @@ public class AdminApp extends Servlet {
|
||||
try {
|
||||
TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now());
|
||||
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) {
|
||||
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");
|
||||
} else {
|
||||
try {
|
||||
WindTrack windTrack = getService().getDomainFactory().trackEvent(event)
|
||||
WindTrack windTrack = getService().getDomainFactory().getTrackedEvent(event)
|
||||
.getTrackedRace(race).getWindTrack(windSource);
|
||||
TimePoint timePoint = getTimePoint(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS,
|
||||
MillisecondsTimePoint.now());
|
||||
|
||||
@@ -169,7 +169,7 @@ public class ModeratorApp extends Servlet {
|
||||
RaceDefinition race = getRaceDefinition(req);
|
||||
TrackedRace trackedRace = null;
|
||||
if (event != null && race != null) {
|
||||
trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race);
|
||||
trackedRace = getService().getDomainFactory().getTrackedEvent(event).getTrackedRace(race);
|
||||
}
|
||||
return trackedRace;
|
||||
}
|
||||
@@ -183,7 +183,7 @@ public class ModeratorApp extends Servlet {
|
||||
} else {
|
||||
try {
|
||||
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);
|
||||
if (sinceUpdateString != null) {
|
||||
System.out.println("Blocking...");
|
||||
@@ -200,6 +200,8 @@ public class ModeratorApp extends Servlet {
|
||||
.asMillis());
|
||||
jsonRace.put("timeofnewestevent", trackedRace.getTimePointOfNewestEvent() == null ? 0l : trackedRace
|
||||
.getTimePointOfNewestEvent().asMillis());
|
||||
jsonRace.put("timeoflastevent", trackedRace.getTimePointOfLastEvent() == null ? 0l : trackedRace
|
||||
.getTimePointOfLastEvent().asMillis());
|
||||
jsonRace.put("updatecount", trackedRace.getUpdateCount());
|
||||
Position positionForWind = null;
|
||||
TrackedLeg currentLeg = trackedRace.getCurrentLeg(timePoint);
|
||||
@@ -234,6 +236,11 @@ public class ModeratorApp extends Servlet {
|
||||
TrackedLegOfCompetitor trackedLegOfCompetitor = leg.getTrackedLeg(competitor);
|
||||
if (trackedLegOfCompetitor != null) {
|
||||
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);
|
||||
if (averageSpeedOverGround != null) {
|
||||
jsonCompetitorInLeg.put("averageSpeedOverGroundInKnots",
|
||||
@@ -347,7 +354,7 @@ public class ModeratorApp extends Servlet {
|
||||
JSONObject jsonRace = new JSONObject();
|
||||
jsonRace.put("name", race.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());
|
||||
JSONArray jsonLegs = new JSONArray();
|
||||
for (Leg leg : race.getCourse().getLegs()) {
|
||||
@@ -356,7 +363,7 @@ public class ModeratorApp extends Servlet {
|
||||
jsonLeg.put("end", leg.getTo().getName());
|
||||
jsonLegs.add(jsonLeg);
|
||||
}
|
||||
TrackedRace trackedRace = getService().getDomainFactory().trackEvent(event).getTrackedRace(race);
|
||||
TrackedRace trackedRace = getService().getDomainFactory().getOrCreateTrackedEvent(event).getTrackedRace(race);
|
||||
if (trackedRace.getStart() != null) {
|
||||
jsonRace.put("start", trackedRace.getStart().asMillis());
|
||||
}
|
||||
|
||||
@@ -12,11 +12,32 @@ import java.text.ParseException;
|
||||
import com.sap.sailing.declination.DeclinationService;
|
||||
import com.sap.sailing.domain.base.Event;
|
||||
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.tractracadapter.DomainFactory;
|
||||
import com.sap.sailing.domain.tractracadapter.RaceHandle;
|
||||
import com.sap.sailing.domain.tractracadapter.RaceTracker;
|
||||
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 {
|
||||
Iterable<Event> getAllEvents();
|
||||
|
||||
@@ -25,38 +46,46 @@ public interface RacingEventService {
|
||||
DomainFactory getDomainFactory();
|
||||
|
||||
/**
|
||||
* Defines the event and for each race listed in the JSON document creates a {@link RaceTracker} that
|
||||
* tracks the respective race. The {@link RaceDefinition}s obtained this way are all grouped into
|
||||
* the single {@link Event} produced for the event listed in the JSON response. Note that the
|
||||
* many race trackers will have their TracTrac <code>Event</code> each, all with the same name,
|
||||
* meaning the same event but being distinct.
|
||||
* Defines the event and for each race listed in the JSON document that is not already being tracked by this service
|
||||
* creates a {@link RaceTracker} that starts tracking the respective race. The {@link RaceDefinition}s obtained this
|
||||
* way are all grouped into the single {@link Event} produced for the event listed in the JSON response. Note that
|
||||
* the many race trackers will have their TracTrac <code>Event</code> each, all with the same name, meaning the same
|
||||
* event but being distinct.
|
||||
*
|
||||
* @param jsonURL
|
||||
* 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
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Adds a single race tracker, using the race's parameter URL which delivers the single configuration
|
||||
* text file for that race. While the result of passing this URL to the TracTrac <code>KeyValue.setup</code>
|
||||
* is a TracTrac <code>Event</code>, those events only manage a single race. In our domain model, we group
|
||||
* those races into a single instance of our {@link Event} class.<p>
|
||||
* If not already tracking the URL/URI/URI combination, adds a single race tracker and starts tracking the race,
|
||||
* using the race's parameter URL which delivers the single configuration text file for that race. While the result
|
||||
* of passing this URL to the TracTrac <code>KeyValue.setup</code> is a TracTrac <code>Event</code>, those events
|
||||
* 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
|
||||
* 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.
|
||||
* @param windStore TODO
|
||||
* exists, it is not added again.<p>
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
|
||||
+43
-17
@@ -16,6 +16,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.declination.DeclinationService;
|
||||
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.tractracadapter.DomainFactory;
|
||||
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.RaceTracker;
|
||||
import com.sap.sailing.expeditionconnector.UDPExpeditionReceiver;
|
||||
@@ -33,11 +35,13 @@ import com.sap.sailing.util.Util.Pair;
|
||||
import com.sap.sailing.util.Util.Triple;
|
||||
|
||||
public class RacingEventServiceImpl implements RacingEventService {
|
||||
private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName());
|
||||
|
||||
private final DomainFactory domainFactory;
|
||||
|
||||
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
|
||||
@@ -45,13 +49,19 @@ public class RacingEventServiceImpl implements RacingEventService {
|
||||
*/
|
||||
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;
|
||||
|
||||
public RacingEventServiceImpl() {
|
||||
domainFactory = DomainFactory.INSTANCE;
|
||||
eventsByName = new HashMap<String, Event>();
|
||||
raceTrackers = new HashMap<Event, Set<RaceTracker>>();
|
||||
raceTrackersByEvent = new HashMap<Event, Set<RaceTracker>>();
|
||||
windTrackers = new HashMap<RaceDefinition, Pair<WindTracker, Integer>>();
|
||||
raceTrackersByURLs = new HashMap<Triple<URL, URI, URI>, RaceTracker>();
|
||||
windReceivers = new HashMap<Integer, UDPExpeditionReceiver>();
|
||||
}
|
||||
|
||||
@@ -71,22 +81,28 @@ public class RacingEventServiceImpl implements RacingEventService {
|
||||
}
|
||||
|
||||
@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);
|
||||
Event event = null;
|
||||
for (RaceRecord rr : jsonService.getRaceRecords()) {
|
||||
URL paramURL = rr.getParamURL();
|
||||
addRace(paramURL, liveURI, storedURI, windStore);
|
||||
event = addRace(paramURL, liveURI, storedURI, windStore).getEvent();
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
@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 {
|
||||
RaceTracker tracker = getDomainFactory().createRaceTracker(paramURL, liveURI, storedURI, windStore);
|
||||
Set<RaceTracker> trackers = raceTrackers.get(tracker.getEvent());
|
||||
Triple<URL, URI, URI> key = new Triple<URL, URI, URI>(paramURL, liveURI, storedURI);
|
||||
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) {
|
||||
trackers = new HashSet<RaceTracker>();
|
||||
raceTrackers.put(tracker.getEvent(), trackers);
|
||||
raceTrackersByEvent.put(tracker.getEvent(), trackers);
|
||||
}
|
||||
trackers.add(tracker);
|
||||
String eventName = tracker.getEvent().getName();
|
||||
@@ -98,15 +114,24 @@ public class RacingEventServiceImpl implements RacingEventService {
|
||||
} else {
|
||||
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
|
||||
public void stopTracking(Event event) throws MalformedURLException, IOException, InterruptedException {
|
||||
if (raceTrackers.containsKey(event)) {
|
||||
for (RaceTracker raceTracker : raceTrackers.get(event)) {
|
||||
raceTracker.stop();
|
||||
if (raceTrackersByEvent.containsKey(event)) {
|
||||
for (RaceTracker raceTracker : raceTrackersByEvent.get(event)) {
|
||||
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) {
|
||||
eventsByName.remove(event.getName());
|
||||
@@ -118,18 +143,19 @@ public class RacingEventServiceImpl implements RacingEventService {
|
||||
|
||||
@Override
|
||||
public void stopTracking(Event event, RaceDefinition race) throws MalformedURLException, IOException, InterruptedException {
|
||||
if (raceTrackers.containsKey(event)) {
|
||||
Iterator<RaceTracker> trackerIter = raceTrackers.get(event).iterator();
|
||||
if (raceTrackersByEvent.containsKey(event)) {
|
||||
Iterator<RaceTracker> trackerIter = raceTrackersByEvent.get(event).iterator();
|
||||
while (trackerIter.hasNext()) {
|
||||
RaceTracker raceTracker = trackerIter.next();
|
||||
if (raceTracker.getRace() == race) {
|
||||
raceTracker.stop();
|
||||
raceTracker.stop(); // this also removes the TrackedRace from trackedEvent
|
||||
trackerIter.remove();
|
||||
raceTrackersByURLs.remove(raceTracker.getURLs());
|
||||
}
|
||||
}
|
||||
}
|
||||
// if the last tracked race was removed, remove the entire event
|
||||
if (raceTrackers.get(event).isEmpty()) {
|
||||
if (raceTrackersByEvent.get(event).isEmpty()) {
|
||||
stopTracking(event);
|
||||
}
|
||||
}
|
||||
@@ -138,7 +164,7 @@ public class RacingEventServiceImpl implements RacingEventService {
|
||||
public synchronized void startTrackingWind(Event event, RaceDefinition race, int port,
|
||||
DeclinationService declinationService) throws SocketException {
|
||||
if (!windTrackers.containsKey(race)) {
|
||||
DynamicTrackedEvent trackedEvent = getDomainFactory().trackEvent(event);
|
||||
DynamicTrackedEvent trackedEvent = getDomainFactory().getOrCreateTrackedEvent(event);
|
||||
DynamicTrackedRace trackedRace = trackedEvent.getTrackedRace(race);
|
||||
WindTracker windTracker = new WindTracker(trackedRace, declinationService);
|
||||
UDPExpeditionReceiver receiver = getOrCreateWindReceiverForPort(port);
|
||||
|
||||
@@ -321,7 +321,7 @@ def liveRaceInformation(configurator):
|
||||
# check for cases where calculations from backend yield
|
||||
# strange numbers - this happens in cases where the competitor
|
||||
# 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)
|
||||
# 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>
|
||||
|
||||
'total': float,
|
||||
'total': int, # total rank
|
||||
'current_rank': int, # current rank for given time
|
||||
'races' : [float, ],
|
||||
'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 += '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'
|
||||
|
||||
# sort competitors by rank in current leg
|
||||
@@ -533,3 +533,69 @@ def adminLiveData(context, request):
|
||||
|
||||
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() {
|
||||
var wHeight = $(window).height();
|
||||
|
||||
$("#rootwrapper").css("height", wHeight);
|
||||
$("#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;
|
||||
border-top: 1px solid #e1e1e1; }
|
||||
#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 {
|
||||
border: none; }
|
||||
#header ul.systeminfo:after {
|
||||
@@ -221,7 +221,7 @@ span.race-count {
|
||||
#main-interface .blue-table .refresh-btn {
|
||||
display: block;
|
||||
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 {
|
||||
display: block;
|
||||
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