mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-28 00:16:46 +00:00
preparation work for a virtual wind track based on wind estimation
This commit is contained in:
+17
-4
@@ -11,8 +11,16 @@ import com.sap.sailing.domain.tracking.TrackedEvent;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.WindSource;
|
||||
import com.sap.sailing.domain.tracking.WindTrack;
|
||||
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
||||
|
||||
public class MongoWindStoreImpl implements MongoWindStore {
|
||||
/**
|
||||
* Stores wind tracks of sources that {@link WindSource#canBeStored() can be stored}. The {@link EmptyWindStore}'s
|
||||
* factory method is used for wind tracks for other sources.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class MongoWindStoreImpl extends EmptyWindStore implements MongoWindStore {
|
||||
private final DB db;
|
||||
private final MongoObjectFactory mongoObjectFactory;
|
||||
private final DomainObjectFactory domainObjectFactory;
|
||||
@@ -31,9 +39,14 @@ public class MongoWindStoreImpl implements MongoWindStore {
|
||||
@Override
|
||||
public WindTrack getWindTrack(TrackedEvent trackedEvent, TrackedRace trackedRace, WindSource windSource,
|
||||
long millisecondsOverWhichToAverage) {
|
||||
WindTrack result = domainObjectFactory.loadWindTrack(trackedEvent.getEvent(),
|
||||
trackedRace.getRace(), windSource, millisecondsOverWhichToAverage);
|
||||
result.addListener(new MongoWindListener(trackedEvent, trackedRace, windSource, mongoObjectFactory, db));
|
||||
WindTrack result;
|
||||
if (windSource.canBeStored()) {
|
||||
result = domainObjectFactory.loadWindTrack(trackedEvent.getEvent(), trackedRace.getRace(), windSource,
|
||||
millisecondsOverWhichToAverage);
|
||||
result.addListener(new MongoWindListener(trackedEvent, trackedRace, windSource, mongoObjectFactory, db));
|
||||
} else {
|
||||
result = super.getWindTrack(trackedEvent, trackedRace, windSource, millisecondsOverWhichToAverage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +166,10 @@ public interface TrackedRace {
|
||||
|
||||
TimePoint getStartOfTracking();
|
||||
|
||||
/**
|
||||
* Regardless of the order in which events were received, this method returns the latest time point contained by any of
|
||||
* the events received and processed.
|
||||
*/
|
||||
TimePoint getTimePointOfNewestEvent();
|
||||
|
||||
NavigableSet<MarkPassing> getMarkPassings(Competitor competitor);
|
||||
@@ -173,7 +177,7 @@ public interface TrackedRace {
|
||||
void removeWind(Wind wind, WindSource windSource);
|
||||
|
||||
/**
|
||||
* Time stamp that the last event received from the underlying push service carried on it.
|
||||
* Time stamp that the event received last from the underlying push service carried on it.
|
||||
* Note that these times may not increase monotonically.
|
||||
*/
|
||||
TimePoint getTimePointOfLastEvent();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.sap.sailing.domain.tracking;
|
||||
|
||||
/**
|
||||
* Possible sources for wind data. Used to key and select between different {@link WindTrack}s.
|
||||
* Possible sources for wind data. Used to key and select between different {@link WindTrack}s. Literals
|
||||
* are given in descending order of precedence. Particularly, the {@link #COURSE_BASED} source should
|
||||
* really only be used if nothing else is known about the wind.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
@@ -10,22 +12,33 @@ public enum WindSource {
|
||||
/**
|
||||
* Manually entered via a web form or received through a REST service call, e.g., from BeTomorrow's estimation
|
||||
*/
|
||||
WEB,
|
||||
WEB(true),
|
||||
|
||||
/**
|
||||
* Measured using wind sensors
|
||||
*/
|
||||
EXPEDITION,
|
||||
|
||||
/**
|
||||
* Inferred from the race course layout if the course is known to have its first leg be an upwind leg
|
||||
*/
|
||||
COURSE_BASED,
|
||||
EXPEDITION(true),
|
||||
|
||||
/**
|
||||
* Estimates wind conditions by analyzing the boat tracks; may not have results for all time points, e.g.,
|
||||
* because at a given time point all boats may sail on the same tack and hence no averaging between the
|
||||
* two tacks is possible. This is the more likely to happen the smaller the fleet tracked is.
|
||||
*/
|
||||
TRACK_BASED_ESTIMATION
|
||||
TRACK_BASED_ESTIMATION(false),
|
||||
|
||||
/**
|
||||
* Inferred from the race course layout if the course is known to have its first leg be an upwind leg
|
||||
*/
|
||||
COURSE_BASED(false);
|
||||
|
||||
private final boolean canBeStored;
|
||||
|
||||
private WindSource(boolean canBeStored) {
|
||||
this.canBeStored = canBeStored;
|
||||
}
|
||||
|
||||
public boolean canBeStored() {
|
||||
return canBeStored;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
package com.sap.sailing.domain.tracking.impl;
|
||||
|
||||
import java.util.NavigableSet;
|
||||
|
||||
import com.sap.sailing.domain.base.TimePoint;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.Wind;
|
||||
import com.sap.sailing.util.impl.ArrayListNavigableSet;
|
||||
import com.sap.sailing.util.impl.UnmodifiableNavigableSet;
|
||||
|
||||
/**
|
||||
* The only "raw" fix produced by this wind track implementation is based on the course layout of the tracked race to
|
||||
* which it is bound in the constructor. Such a fix will only be "emulated" if the
|
||||
* {@link TrackedRace#raceIsKnownToStartUpwind() race is known to start with an upwind leg}. Otherwise, this wind
|
||||
* track will remain empty. The track will also be empty if no {@link TrackedRace#getStart() start time} is known
|
||||
* for the tracked race.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class CourseBasedWindTrackImpl extends WindTrackImpl {
|
||||
private final TrackedRace trackedRace;
|
||||
private static final NavigableSet<Wind> empty = new UnmodifiableNavigableSet<Wind>(new ArrayListNavigableSet<Wind>(WindComparator.INSTANCE));
|
||||
|
||||
public CourseBasedWindTrackImpl(TrackedRace trackedRace, long millisecondsOverWhichToAverage) {
|
||||
super(millisecondsOverWhichToAverage);
|
||||
this.trackedRace = trackedRace;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NavigableSet<Wind> getInternalRawFixes() {
|
||||
NavigableSet<Wind> result;
|
||||
if (trackedRace.raceIsKnownToStartUpwind()) {
|
||||
TimePoint startTime = trackedRace.getStart();
|
||||
if (startTime != null) {
|
||||
result = new ArrayListNavigableSet<Wind>(WindComparator.INSTANCE);
|
||||
result.add(trackedRace.getDirectionFromStartToNextMark(startTime));
|
||||
} else {
|
||||
result = empty;
|
||||
}
|
||||
} else {
|
||||
result = empty;
|
||||
}
|
||||
return empty;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+8
-1
@@ -12,7 +12,14 @@ public class EmptyWindStore implements WindStore {
|
||||
@Override
|
||||
public WindTrack getWindTrack(TrackedEvent trackedEvent, TrackedRace trackedRace, WindSource windSource,
|
||||
long millisecondsOverWhichToAverage) {
|
||||
return new WindTrackImpl(millisecondsOverWhichToAverage);
|
||||
switch (windSource) {
|
||||
case COURSE_BASED:
|
||||
return new CourseBasedWindTrackImpl(trackedRace, millisecondsOverWhichToAverage);
|
||||
case TRACK_BASED_ESTIMATION:
|
||||
return new TrackBasedEstimationWindTrackImpl(trackedRace, millisecondsOverWhichToAverage);
|
||||
default:
|
||||
return new WindTrackImpl(millisecondsOverWhichToAverage);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
package com.sap.sailing.domain.tracking.impl;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import com.sap.sailing.domain.base.TimePoint;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.Wind;
|
||||
import com.sap.sailing.util.impl.AbstractUnmodifiableNavigableSet;
|
||||
import com.sap.sailing.util.impl.DescendingNavigableSet;
|
||||
|
||||
/**
|
||||
* A virtual wind track that computes the wind bearing based on the boat tracks recorded in the tracked
|
||||
* race for which this wind track is constructed. It has a fixed time resolution as defined by the constant
|
||||
* {@link #RESOLUTION_IN_MILLISECONDS}. When asked for the wind at a time at which the wind cannot be estimated,
|
||||
* the raw fixes will have <code>null</code> as value for this time. These <code>null</code> "fixes" are at the
|
||||
* same time considered "outliers" by the {@link #getInternalFixes()} operation which filters them from the
|
||||
* "smoothened" view. With this, the view with "outliers" removed contains all those fixes for which the wind
|
||||
* bearing was successfully computed from the tracked race's boat tracks.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class TrackBasedEstimationWindTrackImpl extends WindTrackImpl {
|
||||
/**
|
||||
* The time resolution is one second.
|
||||
*/
|
||||
private static final long RESOLUTION_IN_MILLISECONDS = 1000l;
|
||||
|
||||
private final EstimatedWindFixesAsNavigableSet virtualInternalRawFixes;
|
||||
|
||||
public TrackBasedEstimationWindTrackImpl(TrackedRace trackedRace, long millisecondsOverWhichToAverage) {
|
||||
super(millisecondsOverWhichToAverage);
|
||||
this.virtualInternalRawFixes = new EstimatedWindFixesAsNavigableSet(trackedRace);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NavigableSet<Wind> getInternalRawFixes() {
|
||||
return virtualInternalRawFixes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NavigableSet<Wind> getInternalFixes() {
|
||||
return new PartialNavigableSetView<Wind>(super.getInternalFixes()) {
|
||||
@Override
|
||||
protected boolean isValid(Wind e) {
|
||||
return e != null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulates a collection of {@link Wind} fixes for a {@link TrackedRace}, computed using
|
||||
* {@link TrackedRace#getEstimatedWindDirection(com.sap.sailing.domain.base.Position, TimePoint)}. If not contrained
|
||||
* by a {@link #from} and/or a {@link #to} time point, an equidistant time field is assumed, starting at
|
||||
* {@link TrackedRace#getStart()} and leading up to {@link TrackedRace#getTimePointOfNewestEvent()}. If
|
||||
* {@link TrackedRace#getStart()} returns <code>null</code>, {@link Long#MAX_VALUE} is used as the {@link #from}
|
||||
* time point, pushing the start to the more or less infinite future ("end of the universe"). If no event was
|
||||
* received yet and hence {@link TrackedRace#getTimePointOfNewestEvent()} returns <code>null</code>, the
|
||||
* {@link #to} end is assumed to be the beginning of the epoch (1970-01-01T00:00:00).
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
private static class EstimatedWindFixesAsNavigableSet extends AbstractUnmodifiableNavigableSet<Wind> {
|
||||
private final TrackedRace trackedRace;
|
||||
|
||||
private final TimePoint from;
|
||||
|
||||
private final TimePoint to;
|
||||
|
||||
public EstimatedWindFixesAsNavigableSet(TrackedRace trackedRace) {
|
||||
this(trackedRace, null, null);
|
||||
}
|
||||
|
||||
private EstimatedWindFixesAsNavigableSet(TrackedRace trackedRace, TimePoint from, TimePoint to) {
|
||||
this.trackedRace = trackedRace;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
private TimePoint lowerToResolution(Wind w) {
|
||||
return new MillisecondsTimePoint((w.getTimePoint().asMillis()-1) / RESOLUTION_IN_MILLISECONDS * RESOLUTION_IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
private TimePoint floorToResolution(Wind w) {
|
||||
return new MillisecondsTimePoint(w.getTimePoint().asMillis() / RESOLUTION_IN_MILLISECONDS * RESOLUTION_IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
private TimePoint ceilingToResolution(Wind w) {
|
||||
return new MillisecondsTimePoint(((w.getTimePoint().asMillis()-1) / RESOLUTION_IN_MILLISECONDS + 1) * RESOLUTION_IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
private TimePoint higherToResolution(Wind w) {
|
||||
return new MillisecondsTimePoint((w.getTimePoint().asMillis() / RESOLUTION_IN_MILLISECONDS + 1) * RESOLUTION_IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
private TimePoint getFrom() {
|
||||
return from == null ? trackedRace.getStart() == null ? new MillisecondsTimePoint(Long.MAX_VALUE)
|
||||
: trackedRace.getStart() : from;
|
||||
}
|
||||
|
||||
private TimePoint getTo() {
|
||||
return to == null ? trackedRace.getTimePointOfNewestEvent() == null ? new MillisecondsTimePoint(0)
|
||||
: trackedRace.getTimePointOfNewestEvent() : to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wind lower(Wind w) {
|
||||
TimePoint timePoint = lowerToResolution(w);
|
||||
return timePoint.compareTo(getFrom()) < 0 ? null : trackedRace.getEstimatedWindDirection(w.getPosition(), timePoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wind floor(Wind w) {
|
||||
TimePoint timePoint = floorToResolution(w);
|
||||
return timePoint.compareTo(getFrom()) < 0 ? null : trackedRace.getEstimatedWindDirection(w.getPosition(), timePoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wind ceiling(Wind w) {
|
||||
TimePoint timePoint = ceilingToResolution(w);
|
||||
return timePoint.compareTo(getTo()) > 0 ? null : trackedRace.getEstimatedWindDirection(w.getPosition(), timePoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wind higher(Wind w) {
|
||||
TimePoint timePoint = higherToResolution(w);
|
||||
return timePoint.compareTo(getTo()) > 0 ? null : trackedRace.getEstimatedWindDirection(w.getPosition(), timePoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Wind> iterator() {
|
||||
return new Iterator<Wind>() {
|
||||
private TimePoint timePoint = getFrom();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return timePoint.compareTo(getTo()) < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wind next() {
|
||||
Wind result = floor(new DummyWind(timePoint));
|
||||
timePoint = new MillisecondsTimePoint(timePoint.asMillis()+RESOLUTION_IN_MILLISECONDS);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableSet<Wind> descendingSet() {
|
||||
return new DescendingNavigableSet<Wind>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Wind> descendingIterator() {
|
||||
return descendingSet().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableSet<Wind> subSet(Wind fromElement, boolean fromInclusive, Wind toElement, boolean toInclusive) {
|
||||
return new EstimatedWindFixesAsNavigableSet(trackedRace, fromInclusive?ceilingToResolution(fromElement):higherToResolution(fromElement),
|
||||
toInclusive?floorToResolution(toElement):lowerToResolution(toElement));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableSet<Wind> headSet(Wind toElement, boolean inclusive) {
|
||||
return new EstimatedWindFixesAsNavigableSet(trackedRace, /* from */ null,
|
||||
inclusive?floorToResolution(toElement):lowerToResolution(toElement));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableSet<Wind> tailSet(Wind fromElement, boolean inclusive) {
|
||||
return new EstimatedWindFixesAsNavigableSet(trackedRace, inclusive?ceilingToResolution(fromElement):higherToResolution(fromElement),
|
||||
/* to */ null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<Wind> subSet(Wind fromElement, Wind toElement) {
|
||||
return subSet(fromElement, true, toElement, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<Wind> headSet(Wind toElement) {
|
||||
return headSet(toElement, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedSet<Wind> tailSet(Wind fromElement) {
|
||||
return tailSet(fromElement, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<? super Wind> comparator() {
|
||||
return WindComparator.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wind first() {
|
||||
return floor(new DummyWind(getFrom()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Wind last() {
|
||||
return ceiling(new DummyWind(getTo()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return (int) ((getTo().asMillis()-getFrom().asMillis())/RESOLUTION_IN_MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
boolean result = false;
|
||||
if (o instanceof Wind) {
|
||||
Wind wind = (Wind) o;
|
||||
result = wind.getTimePoint().asMillis() % RESOLUTION_IN_MILLISECONDS == 0
|
||||
&& wind.getTimePoint().compareTo(getFrom()) >= 0 && wind.getTimePoint().compareTo(getTo()) < 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
Object[] result = new Object[size()];
|
||||
int i=0;
|
||||
for (Wind w : this) {
|
||||
result[i++] = w;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
Object[] result = a;
|
||||
if (result.length < size()) {
|
||||
result = new Object[size()];
|
||||
}
|
||||
int i=0;
|
||||
for (Wind w : this) {
|
||||
result[i++] = w;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] tResult = (T[]) result;
|
||||
return tResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ public abstract class TrackImpl<FixType extends Timed> implements Track<FixType>
|
||||
*/
|
||||
private final NavigableSet<Timed> fixes;
|
||||
|
||||
protected class DummyTimed implements Timed {
|
||||
protected static class DummyTimed implements Timed {
|
||||
private final TimePoint timePoint;
|
||||
public DummyTimed(TimePoint timePoint) {
|
||||
super();
|
||||
|
||||
+1
-1
@@ -203,7 +203,7 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
|
||||
result.append("\t");
|
||||
}
|
||||
|
||||
private class DummyWind extends DummyTimed implements Wind {
|
||||
protected static class DummyWind extends DummyTimed implements Wind {
|
||||
public DummyWind(TimePoint timePoint) {
|
||||
super(timePoint);
|
||||
}
|
||||
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
package com.sap.sailing.util.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.NavigableSet;
|
||||
|
||||
public abstract class AbstractUnmodifiableNavigableSet<E> implements NavigableSet<E> {
|
||||
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E pollFirst() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E pollLast() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
for (Object o : c) {
|
||||
if (contains(o)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
}
|
||||
+1
-41
@@ -12,7 +12,7 @@ import java.util.SortedSet;
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*/
|
||||
public class UnmodifiableNavigableSet<E> implements NavigableSet<E> {
|
||||
public class UnmodifiableNavigableSet<E> extends AbstractUnmodifiableNavigableSet<E> implements NavigableSet<E> {
|
||||
private final NavigableSet<E> set;
|
||||
|
||||
public UnmodifiableNavigableSet(NavigableSet<E> delegate) {
|
||||
@@ -59,41 +59,11 @@ public class UnmodifiableNavigableSet<E> implements NavigableSet<E> {
|
||||
return set.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return set.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E lower(E e) {
|
||||
return set.lower(e);
|
||||
@@ -114,16 +84,6 @@ public class UnmodifiableNavigableSet<E> implements NavigableSet<E> {
|
||||
return set.higher(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E pollFirst() {
|
||||
return set.pollFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E pollLast() {
|
||||
return set.pollLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
final Iterator<E> setIterator = set.iterator();
|
||||
|
||||
+142
-4
@@ -7,12 +7,31 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ca.nanometrics.gflot.client.Axis;
|
||||
import ca.nanometrics.gflot.client.PlotItem;
|
||||
import ca.nanometrics.gflot.client.PlotModelStrategy;
|
||||
import ca.nanometrics.gflot.client.PlotPosition;
|
||||
import ca.nanometrics.gflot.client.PlotWithOverview;
|
||||
import ca.nanometrics.gflot.client.PlotWithOverviewModel;
|
||||
import ca.nanometrics.gflot.client.event.PlotHoverListener;
|
||||
import ca.nanometrics.gflot.client.event.SelectionListener;
|
||||
import ca.nanometrics.gflot.client.jsni.Plot;
|
||||
import ca.nanometrics.gflot.client.options.AxisOptions;
|
||||
import ca.nanometrics.gflot.client.options.GridOptions;
|
||||
import ca.nanometrics.gflot.client.options.LegendOptions;
|
||||
import ca.nanometrics.gflot.client.options.LineSeriesOptions;
|
||||
import ca.nanometrics.gflot.client.options.PlotOptions;
|
||||
import ca.nanometrics.gflot.client.options.PointsSeriesOptions;
|
||||
import ca.nanometrics.gflot.client.options.SelectionOptions;
|
||||
import ca.nanometrics.gflot.client.options.TickFormatter;
|
||||
|
||||
import com.google.gwt.cell.client.ActionCell;
|
||||
import com.google.gwt.cell.client.ActionCell.Delegate;
|
||||
import com.google.gwt.event.dom.client.ChangeEvent;
|
||||
import com.google.gwt.event.dom.client.ChangeHandler;
|
||||
import com.google.gwt.event.logical.shared.ValueChangeEvent;
|
||||
import com.google.gwt.event.logical.shared.ValueChangeHandler;
|
||||
import com.google.gwt.i18n.client.DateTimeFormat;
|
||||
import com.google.gwt.user.cellview.client.CellTable;
|
||||
import com.google.gwt.user.cellview.client.ColumnSortEvent;
|
||||
import com.google.gwt.user.cellview.client.ColumnSortEvent.Handler;
|
||||
@@ -30,6 +49,7 @@ import com.google.gwt.user.client.ui.HorizontalPanel;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.ListBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.google.gwt.view.client.ListDataProvider;
|
||||
import com.sap.sailing.gwt.ui.shared.EventDAO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceDAO;
|
||||
@@ -59,12 +79,15 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
|
||||
private final Map<String, ListDataProvider<WindDAO>> windLists;
|
||||
private final CheckBox showEstimatedWindBox;
|
||||
private final CheckBox raceIsKnownToStartUpwindBox;
|
||||
private final Widget stripChart;
|
||||
private final DateTimeFormat dateFormat;
|
||||
|
||||
public WindPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
|
||||
EventRefresher eventRefresher, StringConstants stringConstants) {
|
||||
this.sailingService = sailingService;
|
||||
this.errorReporter = errorReporter;
|
||||
this.stringConstants = stringConstants;
|
||||
dateFormat = DateTimeFormat.getFormat("HH:mm:ss");
|
||||
windLists = new HashMap<String, ListDataProvider<WindDAO>>();
|
||||
windSourceSelection = new ListBox();
|
||||
windSourceSelection.addChangeHandler(new ChangeHandler() {
|
||||
@@ -123,11 +146,12 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
|
||||
return ""+object.dampenedTrueWindFromDeg;
|
||||
}
|
||||
};
|
||||
grid = new Grid(3, 2); // first row: event/race selection; second row: wind source selection; third row: wind display
|
||||
grid = new Grid(4, 2); // first row: event/race selection; second row: wind source selection; third row: wind display
|
||||
trackedEventsComposite = new TrackedEventsComposite(sailingService, errorReporter, eventRefresher, stringConstants, false);
|
||||
trackedEventsComposite.addRaceSelectionChangeListener(this);
|
||||
grid.setWidget(0, 0, trackedEventsComposite);
|
||||
windSettingPanel = new WindSettingPanel(sailingService, errorReporter, trackedEventsComposite, this);
|
||||
grid.setWidget(0, 1, windSettingPanel);
|
||||
HorizontalPanel windSourceSelectionPanel = new HorizontalPanel();
|
||||
windSourceSelectionPanel.setSpacing(10);
|
||||
windSourceSelectionPanel.add(new Label(stringConstants.windSource()));
|
||||
@@ -153,11 +177,125 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
|
||||
}
|
||||
});
|
||||
grid.setWidget(1, 0, windSourceSelectionPanel);
|
||||
grid.setWidget(2, 1, windSettingPanel);
|
||||
stripChart = createStripChart();
|
||||
grid.setWidget(2, 0, stripChart);
|
||||
grid.getCellFormatter().setVerticalAlignment(1, 1, HasVerticalAlignment.ALIGN_TOP);
|
||||
this.setWidget(grid);
|
||||
}
|
||||
|
||||
private Widget createStripChart() {
|
||||
PlotWithOverviewModel model = new PlotWithOverviewModel(PlotModelStrategy.defaultStrategy());
|
||||
PlotOptions plotOptions = new PlotOptions();
|
||||
plotOptions.setDefaultLineSeriesOptions(new LineSeriesOptions().setLineWidth(1).setShow(true));
|
||||
plotOptions.setDefaultPointsOptions(new PointsSeriesOptions().setShow(false));
|
||||
plotOptions.setDefaultShadowSize(2);
|
||||
AxisOptions hAxisOptions = new AxisOptions();
|
||||
hAxisOptions.setTickFormatter(new TickFormatter() {
|
||||
@Override
|
||||
public String formatTickValue(double tickValue, Axis axis) {
|
||||
return dateFormat.format(new Date((long) tickValue));
|
||||
}
|
||||
});
|
||||
plotOptions.setXAxisOptions(hAxisOptions);
|
||||
plotOptions.setLegendOptions(new LegendOptions().setShow(false));
|
||||
plotOptions.setGridOptions(new GridOptions().setHoverable(true).setMouseActiveRadius(5).setAutoHighlight(true));
|
||||
plotOptions.setSelectionOptions(new SelectionOptions().setDragging(true).setMode("x")); // select along x-axis only
|
||||
/*
|
||||
for (int i = 0; i < competitorsAndTimePointsDAO.getCompetitor().length; i++){
|
||||
SeriesHandler series = model.addSeries(""+i, getColorByID(i));
|
||||
series.setOptions(SeriesType.LINES, new LineSeriesOptions().setLineWidth(2.5).setShow(true));
|
||||
series.setOptions(SeriesType.POINTS, new PointsSeriesOptions().setLineWidth(0).setShow(false));
|
||||
series.setVisible(false);
|
||||
seriesID.add(series);
|
||||
series = model.addSeries(i + " passed mark", getColorByID(i));
|
||||
series.setOptions(SeriesType.LINES, new LineSeriesOptions().setLineWidth(0).setShow(false));
|
||||
series.setOptions(SeriesType.POINTS, new PointsSeriesOptions().setLineWidth(3).setShow(true));
|
||||
series.setVisible(false);
|
||||
markSeriesID.add(series);
|
||||
}
|
||||
*/
|
||||
final PlotWithOverview plot = new PlotWithOverview(model, plotOptions) {
|
||||
@Override
|
||||
protected void onLoad() {
|
||||
super.onLoad();
|
||||
// onLoad is called immediately after a widget becomes attached to the browser's document.
|
||||
// Use this time point to ask the chart to redraw
|
||||
redraw();
|
||||
}
|
||||
};
|
||||
// add hover listener
|
||||
plot.addHoverListener(new PlotHoverListener() {
|
||||
public void onPlotHover(Plot plot, PlotPosition position, PlotItem item) {
|
||||
/*
|
||||
CompetitorDAO competitor = competitorID.get(seriesID.indexOf(item.getSeries()));
|
||||
if (item != null && competitor != null) {
|
||||
if (item.getSeries().getLabel().toLowerCase().contains("mark")){
|
||||
selectedPointLabel.setText(competitor.name + " passed " + markPassingBuoyName.get(competitor.id + (long) item.getDataPoint().getX()) +" at " + dateFormat.format(new Date((long) item.getDataPoint().getX())));
|
||||
}
|
||||
else {
|
||||
String unit = "";
|
||||
switch (dataToShow){
|
||||
case CURRENT_SPEED_OVER_GROUND_IN_KNOTS:
|
||||
unit = stringConstants.currentSpeedOverGroundInKnotsUnit();
|
||||
break;
|
||||
case DISTANCE_TRAVELED:
|
||||
unit = stringConstants.distanceInMetersUnit();
|
||||
break;
|
||||
case GAP_TO_LEADER_IN_SECONDS:
|
||||
unit = stringConstants.gapToLeaderInSecondsUnit();
|
||||
break;
|
||||
case VELOCITY_MADE_GOOD_IN_KNOTS:
|
||||
unit = stringConstants.velocityMadeGoodInKnotsUnit();
|
||||
break;
|
||||
case WINDWARD_DISTANCE_TO_OVERALL_LEADER:
|
||||
unit = stringConstants.windwardDistanceToGoInMetersUnit();
|
||||
}
|
||||
String decimalPlaces = "";
|
||||
for (int i = 0; i < dataToShow.getPrecision(); i++){
|
||||
if (i == 0){
|
||||
decimalPlaces += ".";
|
||||
}
|
||||
decimalPlaces += "0";
|
||||
}
|
||||
NumberFormat numberFormat = NumberFormat.getFormat("0" +decimalPlaces);
|
||||
selectedPointLabel.setText(competitor.name + " at " + dateFormat.format(new Date((long) item.getDataPoint().getX()))
|
||||
+ ": " + numberFormat.format(item.getDataPoint().getY()) + unit);
|
||||
}
|
||||
} else {
|
||||
selectedPointLabel.setText(stringConstants.noSelection());
|
||||
}
|
||||
*/
|
||||
}
|
||||
}, true);
|
||||
plot.addSelectionListener(new SelectionListener() {
|
||||
|
||||
public void selected(double x1, double y1, double x2, double y2) {
|
||||
/* TODO Remove not visible buoys from the series when user is zooming in or add them if he is zooming out.
|
||||
for (CompetitorDAO competitor : competitorsAndTimePointsDAO.getCompetitor()){
|
||||
long[] markPassingTimes = competitorsAndTimePointsDAO.getMarkPassings(competitor);
|
||||
Double[] markPassingValues = chartData.getMarkPassings(competitor);
|
||||
SeriesHandler markSeries = getCompetitorMarkPassingSeries(competitor);
|
||||
markSeries.clear();
|
||||
int visibleMarkPassings = 0;
|
||||
for (int j = 0; j < markPassingTimes.length; j++){
|
||||
if (markPassingValues[j] != null && markPassingTimes[j] > x1 && markPassingTimes[j] < x2) {
|
||||
markSeries.add(new DataPoint(markPassingTimes[j],markPassingValues[j]));
|
||||
visibleMarkPassings++;
|
||||
}
|
||||
}
|
||||
if (visibleMarkPassings == 0){
|
||||
markSeries.setVisible(false);
|
||||
}
|
||||
}*/
|
||||
plot.setLinearSelection(x1, x2);
|
||||
}
|
||||
});
|
||||
// plot.setHeight(height);
|
||||
// plot.setWidth(width);
|
||||
plot.setOverviewHeight(60);
|
||||
return plot;
|
||||
}
|
||||
|
||||
private void clearOrShowWindBasedOnRaceSelection(List<Triple<EventDAO, RegattaDAO, RaceDAO>> selectedRaces) {
|
||||
if (selectedRaces.isEmpty()) {
|
||||
clearWindDisplay(); // no wind known for untracked race
|
||||
@@ -227,9 +365,9 @@ public class WindPanel extends FormPanel implements EventDisplayer, WindShower,
|
||||
|
||||
private void showWindForRace(WindInfoForRaceDAO result) {
|
||||
raceIsKnownToStartUpwindBox.setValue(result.raceIsKnownToStartUpwind);
|
||||
grid.setWidget(2, 0, null);
|
||||
grid.setWidget(3, 0, null);
|
||||
VerticalPanel windDisplay = new VerticalPanel();
|
||||
grid.setWidget(2, 0, windDisplay);
|
||||
grid.setWidget(3, 0, windDisplay);
|
||||
for (Map.Entry<String, WindTrackInfoDAO> e : result.windTrackInfoByWindSourceName.entrySet()) {
|
||||
Label windSourceLabel = new Label(stringConstants.windSource()+": "+e.getKey()+
|
||||
", "+stringConstants.dampeningInterval()+" "+e.getValue().dampeningIntervalInMilliseconds+"ms");
|
||||
|
||||
Reference in New Issue
Block a user