preparation work for a virtual wind track based on wind estimation

This commit is contained in:
Axel Uhl
2011-12-23 13:47:00 +01:00
parent 37a9751d85
commit de63aed8fd
11 changed files with 566 additions and 62 deletions
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
}
@@ -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();
@@ -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);
}
@@ -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;
}
}
@@ -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();
@@ -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");