diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageRotator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageRotator.java index 88e22340dec..38316668348 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageRotator.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageRotator.java @@ -71,7 +71,7 @@ public class ImageRotator { context.translate(canvasRadius, canvasRadius); context.rotate(angleInRadians); context.scale(scaleFactor, scaleFactor); - context.drawImage(imageElement, -imageWidth/2, -imageHeight/2); + context.drawImage(imageElement, (-imageWidth/2) * scaleFactor, (-imageHeight/2) * scaleFactor); result = canvas.toDataUrl("image/png"); context.restore(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMap.java new file mode 100644 index 00000000000..e2920590d31 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMap.java @@ -0,0 +1,844 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.maps.client.InfoWindowContent; +import com.google.gwt.maps.client.MapType; +import com.google.gwt.maps.client.MapWidget; +import com.google.gwt.maps.client.Maps; +import com.google.gwt.maps.client.control.LargeMapControl3D; +import com.google.gwt.maps.client.control.MenuMapTypeControl; +import com.google.gwt.maps.client.control.ScaleControl; +import com.google.gwt.maps.client.event.MapDragEndHandler; +import com.google.gwt.maps.client.event.MapMouseMoveHandler; +import com.google.gwt.maps.client.event.MapZoomEndHandler; +import com.google.gwt.maps.client.event.MarkerClickHandler; +import com.google.gwt.maps.client.event.MarkerMouseOutHandler; +import com.google.gwt.maps.client.event.MarkerMouseOverHandler; +import com.google.gwt.maps.client.event.PolylineClickHandler; +import com.google.gwt.maps.client.event.PolylineMouseOutHandler; +import com.google.gwt.maps.client.event.PolylineMouseOverHandler; +import com.google.gwt.maps.client.geom.LatLng; +import com.google.gwt.maps.client.geom.LatLngBounds; +import com.google.gwt.maps.client.geom.Point; +import com.google.gwt.maps.client.geom.Projection; +import com.google.gwt.maps.client.overlay.Icon; +import com.google.gwt.maps.client.overlay.Marker; +import com.google.gwt.maps.client.overlay.MarkerOptions; +import com.google.gwt.maps.client.overlay.Polyline; +import com.google.gwt.maps.client.overlay.PolylineOptions; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.common.Util.Triple; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.Timer; +import com.sap.sailing.gwt.ui.shared.CompetitorDAO; +import com.sap.sailing.gwt.ui.shared.EventDAO; +import com.sap.sailing.gwt.ui.shared.GPSFixDAO; +import com.sap.sailing.gwt.ui.shared.ManeuverDAO; +import com.sap.sailing.gwt.ui.shared.MarkDAO; +import com.sap.sailing.gwt.ui.shared.RaceDAO; +import com.sap.sailing.gwt.ui.shared.RegattaDAO; +import com.sap.sailing.server.api.EventNameAndRaceName; + +public class RaceMap { + protected MapWidget map; + + private final SailingServiceAsync sailingService; + private final ErrorReporter errorReporter; + + /** + * Two sails on downwind leg, wind from port (sails on starboard); no highlighting + */ + protected ImageRotator boatIconDownwindPortRotator; + + /** + * Two sails on downwind leg, wind from port (sails on starboard); with highlighting + */ + protected ImageRotator boatIconHighlightedDownwindPortRotator; + + /** + * Two sails on downwind leg, wind from starboard (sails on port); no highlighting + */ + protected ImageRotator boatIconDownwindStarboardRotator; + + /** + * Two sails on downwind leg, wind from starboard (sails on port); with highlighting + */ + protected ImageRotator boatIconHighlightedDownwindStarboardRotator; + + /** + * One sail, wind from port (sails on starboard); no highlighting + */ + protected ImageRotator boatIconPortRotator; + + /** + * One sail, wind from port (sails on starboard); with highlighting + */ + protected ImageRotator boatIconHighlightedPortRotator; + + /** + * One sail, wind from starboard (sails on port); no highlighting + */ + protected ImageRotator boatIconStarboardRotator; + + /** + * One sail, wind from starboard (sails on port); with highlighting + */ + protected ImageRotator boatIconHighlightedStarboardRotator; + + protected Icon buoyIcon; + protected Icon tackToStarboardIcon; + protected Icon tackToPortIcon; + protected Icon jibeToStarboardIcon; + protected Icon jibeToPortIcon; + protected Icon markPassingToStarboardIcon; + protected Icon markPassingToPortIcon; + protected Icon headUpOnStarboardIcon; + protected Icon headUpOnPortIcon; + protected Icon bearAwayOnStarboardIcon; + protected Icon bearAwayOnPortIcon; + protected Icon unknownManeuverIcon; + protected Icon penaltyCircleToStarboardIcon; + protected Icon penaltyCircleToPortIcon; + protected LatLng lastMousePosition; + + protected long tailLengthInMilliSeconds = 30000l; + + protected List> selectedEventAndRace; + + /** + * If the user explicitly zoomed or panned the map, don't adjust zoom/pan unless a new race is selected + */ + protected boolean mapZoomedOrPannedSinceLastRaceSelectionChange = false; + + /** + * Used to check if the first initial zoom to the buoy markers was already done. + */ + protected boolean mapFirstZoomDone = false; + + + // key for domain web4sap.com + protected final String mapsAPIKey = "ABQIAAAAmvjPh3ZpHbnwuX3a66lDqRRLCigyC_gRDASMpyomD2do5awpNhRCyD_q-27hwxKe_T6ivSZ_0NgbUg"; + + protected static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + + protected final RaceMapData data; + + private final Timer timer; + + protected boolean showDouglasPeuckerPoints = false; + + protected boolean showManeuverHeadUp = false; + + protected boolean showManeuverBearAway = false; + + protected boolean showManeuverTack = false; + + protected boolean showManeuverJibe = false; + + protected boolean showManeuverPenaltyCircle = false; + + protected boolean showManeuverMarkPassing = false; + + protected boolean showManeuverOther = false; + + public RaceMap(SailingServiceAsync sailingService, ErrorReporter errorReporter, Timer timer) { + this.sailingService = sailingService; + this.errorReporter = errorReporter; + this.timer = timer; + + data = new RaceMapData(); + } + + public void loadMapsAPI(final Grid grid, final int gridRow, final int gridColumn) { + Maps.loadMapsApi(mapsAPIKey, "2", false, new Runnable() { + public void run() { + map = new MapWidget(); + map.addControl(new LargeMapControl3D()); + map.addControl(new MenuMapTypeControl()); + map.addControl(new ScaleControl()); + // Add the map to the HTML host page + map.setSize("100%", "100%"); + map.setScrollWheelZoomEnabled(true); + map.setContinuousZoom(true); + + MapType currentMapType = map.getCurrentMapType(); + Projection projection = currentMapType.getProjection(); + + grid.setWidget(gridRow, gridColumn, map); + + map.addMapZoomEndHandler(new MapZoomEndHandler() { + @Override + public void onZoomEnd(MapZoomEndEvent event) { + mapZoomedOrPannedSinceLastRaceSelectionChange = true; + } + }); + map.addMapDragEndHandler(new MapDragEndHandler() { + @Override + public void onDragEnd(MapDragEndEvent event) { + mapZoomedOrPannedSinceLastRaceSelectionChange = true; + } + }); + map.addMapMouseMoveHandler(new MapMouseMoveHandler() { + @Override + public void onMouseMove(MapMouseMoveEvent event) { + lastMousePosition = event.getLatLng(); + } + }); + boatIconDownwindPortRotator = new ImageRotator(resources.lowlightedBoatIconDW_Port()); + boatIconHighlightedDownwindPortRotator = new ImageRotator(resources.highlightedBoatIconDW_Port()); + boatIconDownwindStarboardRotator = new ImageRotator(resources.lowlightedBoatIconDW_Starboard()); + boatIconHighlightedDownwindStarboardRotator = new ImageRotator(resources + .highlightedBoatIconDW_Starboard()); + boatIconPortRotator = new ImageRotator(resources.lowlightedBoatIcon_Port()); + boatIconHighlightedPortRotator = new ImageRotator(resources.highlightedBoatIcon_Port()); + boatIconStarboardRotator = new ImageRotator(resources.lowlightedBoatIcon_Starboard()); + boatIconHighlightedStarboardRotator = new ImageRotator(resources.highlightedBoatIcon_Starboard()); + buoyIcon = Icon.newInstance(resources.buoyIcon().getSafeUri().asString()); + buoyIcon.setIconAnchor(Point.newInstance(4, 4)); + tackToStarboardIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|00FF00|000000"); + tackToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); + tackToPortIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|FF0000|000000"); + tackToPortIcon.setIconAnchor(Point.newInstance(10, 33)); + jibeToStarboardIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=J|00FF00|000000"); + jibeToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); + jibeToPortIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=J|FF0000|000000"); + jibeToPortIcon.setIconAnchor(Point.newInstance(10, 33)); + headUpOnStarboardIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=H|00FF00|000000"); + headUpOnStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); + headUpOnPortIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=H|FF0000|000000"); + headUpOnPortIcon.setIconAnchor(Point.newInstance(10, 33)); + bearAwayOnStarboardIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=B|00FF00|000000"); + bearAwayOnStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); + bearAwayOnPortIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=B|FF0000|000000"); + bearAwayOnPortIcon.setIconAnchor(Point.newInstance(10, 33)); + markPassingToStarboardIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=M|00FF00|000000"); + markPassingToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); + markPassingToPortIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=M|FF0000|000000"); + markPassingToPortIcon.setIconAnchor(Point.newInstance(10, 33)); + unknownManeuverIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=?|FFFFFF|000000"); + unknownManeuverIcon.setIconAnchor(Point.newInstance(10, 33)); + penaltyCircleToStarboardIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|00FF00|000000"); + penaltyCircleToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); + penaltyCircleToPortIcon = Icon + .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|FF0000|000000"); + penaltyCircleToPortIcon.setIconAnchor(Point.newInstance(10, 33)); + } + }); + } + + /** + * From {@link #data.fixes} as well as the selection of {@link #getCompetitorsToShow competitors to show}, computes the + * from/to times for which to request GPS raceMapData.fixes from the server. No update is performed here to {@link #data.fixes}. The + * result guarantees that, when used in + * {@link SailingServiceAsync#getBoatPositions(String, String, Map, Map, boolean, AsyncCallback)}, for each + * competitor from {@link #competitorsToShow} there are all raceMapData.fixes known by the server for that competitor starting + * at upTo-{@link #tailLengthInMilliSeconds} and ending at upTo (exclusive). + * + * @return a triple whose {@link Triple#getA() first} component contains the "from", and whose {@link Triple#getB() + * second} component contains the "to" times for the competitors whose trails / positions to show; the + * {@link Triple#getC() third} component tells whether the existing raceMapData.fixes can remain and be augmented by + * those requested or need to be replaced + */ + protected Triple, Map, Map> computeFromAndTo( + Date upTo, Collection competitorsToShow) { + Date tailstart = new Date(upTo.getTime() - tailLengthInMilliSeconds); + Map from = new HashMap(); + Map to = new HashMap(); + Map overlapWithKnownFixes = new HashMap(); + + for (CompetitorDAO competitor : competitorsToShow) { + List fixesForCompetitor = data.fixes.get(competitor); + Date fromDate; + Date toDate; + Date timepointOfLastKnownFix = fixesForCompetitor == null ? null + : data.getTimepointOfLastNonExtrapolated(fixesForCompetitor); + Date timepointOfFirstKnownFix = fixesForCompetitor == null ? null + : data.getTimepointOfFirstNonExtrapolated(fixesForCompetitor); + boolean overlap = false; + if (fixesForCompetitor != null && timepointOfFirstKnownFix != null + && !tailstart.before(timepointOfFirstKnownFix) && timepointOfLastKnownFix != null + && !tailstart.after(timepointOfLastKnownFix)) { + // the beginning of what we need is contained in the interval we already have; skip what we already have + fromDate = timepointOfLastKnownFix; + overlap = true; + } else { + fromDate = tailstart; + } + if (fixesForCompetitor != null && timepointOfFirstKnownFix != null + && !upTo.before(timepointOfFirstKnownFix) && timepointOfLastKnownFix != null + && !upTo.after(timepointOfLastKnownFix)) { + // the end of what we need is contained in the interval we already have; skip what we already have + toDate = timepointOfFirstKnownFix; + overlap = true; + } else { + toDate = upTo; + } + // only request something for the competitor if we're missing information at all + if (fromDate.before(toDate) || fromDate.equals(toDate)) { + from.put(competitor, fromDate); + to.put(competitor, toDate); + overlapWithKnownFixes.put(competitor, overlap); + } + } + return new Triple, Map, Map>(from, to, + overlapWithKnownFixes); + } + + + /** + * Adds the raceMapData.fixes received in result to {@link #data.fixes} and ensures they are still contiguous for each + * competitor. If overlapsWithKnownraceMapData.fixes indicates that the raceMapData.fixes received in result + * overlap with those already known, the raceMapData.fixes are merged into the list of already known raceMapData.fixes for the competitor. + * Otherwise, the raceMapData.fixes received in result replace those known so far for the respective competitor. + */ + protected void updateFixes(Map> result, + Map overlapsWithKnownFixes) { + for (Map.Entry> e : result.entrySet()) { + if (e.getValue() != null && !e.getValue().isEmpty()) { + List fixesForCompetitor = data.fixes.get(e.getKey()); + if (fixesForCompetitor == null) { + fixesForCompetitor = new ArrayList(); + data.fixes.put(e.getKey(), fixesForCompetitor); + } + if (!overlapsWithKnownFixes.get(e.getKey())) { + fixesForCompetitor.clear(); + // to re-establish the invariants for raceMapData.tails, raceMapData.firstShownFix and raceMapData.lastShownFix, we now need to remove + // all + // points from the competitor's polyline and clear the entries in raceMapData.firstShownFix and raceMapData.lastShownFix + if (map != null && data.tails.containsKey(e.getKey())) { + map.removeOverlay(data.tails.remove(e.getKey())); + } + data.firstShownFix.remove(e.getKey()); + data.lastShownFix.remove(e.getKey()); + fixesForCompetitor.addAll(e.getValue()); + } else { + data.mergeFixes(e.getKey(), e.getValue()); + } + } + } + } + + protected void showMarksOnMap(List result) { + if (map != null) { + Set toRemove = new HashSet(data.buoyMarkers.keySet()); + for (MarkDAO markDAO : result) { + Marker buoyMarker = data.buoyMarkers.get(markDAO); + if (buoyMarker == null) { + buoyMarker = createBuoyMarker(markDAO); + data.buoyMarkers.put(markDAO, buoyMarker); + map.addOverlay(buoyMarker); + } else { + buoyMarker.setLatLng(LatLng.newInstance(markDAO.position.latDeg, markDAO.position.lngDeg)); + toRemove.remove(markDAO); + } + } + for (MarkDAO toRemoveMarkDAO : toRemove) { + Marker marker = data.buoyMarkers.remove(toRemoveMarkDAO); + map.removeOverlay(marker); + } + zoomMapFirstTimeToMarks(data.buoyMarkers.keySet()); + } + } + +/** + * Zooms the map to the given marks if the map was not zoomed yet. If the map zoom to the marks was successful one + * time, the initial zoom to these marks can not be done again except the race selection is changed via {@link RaceMapPanel#onRaceSelectionChange(List). + * + * @param marksToZoomAt + * the marks to zoom at + */ + protected void zoomMapFirstTimeToMarks(Set marksToZoomAt) { + if (!mapZoomedOrPannedSinceLastRaceSelectionChange && !mapFirstZoomDone) { + LatLngBounds newBounds = null; + if (marksToZoomAt != null && !marksToZoomAt.isEmpty()) { + for (MarkDAO markDAO : marksToZoomAt) { + LatLng latLngZoomFirstTime = LatLng.newInstance(markDAO.position.latDeg, markDAO.position.lngDeg); + LatLngBounds bounds = LatLngBounds.newInstance(latLngZoomFirstTime, latLngZoomFirstTime); + if (newBounds == null) { + newBounds = bounds; + } else { + newBounds.extend(bounds.getNorthEast()); + newBounds.extend(bounds.getSouthWest()); + } + } + + } + if (newBounds != null) { + map.setZoomLevel(map.getBoundsZoomLevel(newBounds)); + map.setCenter(newBounds.getCenter()); + map.checkResizeAndCenter(); + mapFirstZoomDone = true; + /* + * Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that the map was just zoomed + * to the bounds of the buoys, it was not a zoom or pan triggered by the user. As a consequence the + * mapZoomedOrPannedSinceLastRaceSelection option has to reset again. + */ + mapZoomedOrPannedSinceLastRaceSelectionChange = false; + } + } + } + + /** + * @param from + * time point for first fix to show in raceMapData.tails + * @param to + * time point for last fix to show in raceMapData.tails + */ + protected void showBoatsOnMap(Date from, Date to, Collection competitorsToShow, + Set competitorsSelectedInMap) { + if (map != null) { + LatLngBounds newMapBounds = null; + Set competitorDAOsOfUnusedTails = new HashSet(data.tails.keySet()); + Set competitorDAOsOfUnusedMarkers = new HashSet(data.boatMarkers.keySet()); + + for (CompetitorDAO competitorDAO : competitorsToShow) { + if (data.fixes.containsKey(competitorDAO)) { + Polyline tail = data.tails.get(competitorDAO); + if (tail == null) { + tail = createTailAndUpdateIndices(competitorDAO, from, to); + map.addOverlay(tail); + } else { + data.updateTail(tail, competitorDAO, from, to); + competitorDAOsOfUnusedTails.remove(competitorDAO); + } + LatLngBounds bounds = tail.getBounds(); + if (newMapBounds == null) { + newMapBounds = bounds; + } else { + newMapBounds.extend(bounds.getNorthEast()); + newMapBounds.extend(bounds.getSouthWest()); + } + if (data.lastShownFix.containsKey(competitorDAO) && data.lastShownFix.get(competitorDAO) != -1) { + GPSFixDAO lastPos = data.getBoatFix(competitorDAO); + Marker boatMarker = data.boatMarkers.get(competitorDAO); + if (boatMarker == null) { + boatMarker = createBoatMarker(competitorDAO, false); + map.addOverlay(boatMarker); + data.boatMarkers.put(competitorDAO, boatMarker); + } else { + competitorDAOsOfUnusedMarkers.remove(competitorDAO); + // check if anchors match; re-use marker with setImage only if anchors match + Point newAnchor = getBoatImageRotator(lastPos, + competitorsSelectedInMap.contains(competitorDAO)).getAnchor(); + Point oldAnchor = boatMarker.getIcon().getIconAnchor(); + if (oldAnchor.getX() == newAnchor.getX() && oldAnchor.getY() == newAnchor.getY()) { + boatMarker.setLatLng(LatLng.newInstance(lastPos.position.latDeg, + lastPos.position.lngDeg)); + boatMarker.setImage(getBoatImageURL(lastPos, + competitorsSelectedInMap.contains(competitorDAO))); + } else { + // anchors don't match; replace marker + map.removeOverlay(boatMarker); + boatMarker = createBoatMarker(competitorDAO, + competitorsSelectedInMap.contains(competitorDAO)); + map.addOverlay(boatMarker); + data.boatMarkers.put(competitorDAO, boatMarker); + } + } + } + } + } + if (!mapZoomedOrPannedSinceLastRaceSelectionChange && newMapBounds != null) { + map.setZoomLevel(map.getBoundsZoomLevel(newMapBounds)); + map.setCenter(newMapBounds.getCenter()); + mapFirstZoomDone = true; + } + for (CompetitorDAO unusedMarkerCompetitorDAO : competitorDAOsOfUnusedMarkers) { + map.removeOverlay(data.boatMarkers.remove(unusedMarkerCompetitorDAO)); + } + for (CompetitorDAO unusedTailCompetitorDAO : competitorDAOsOfUnusedTails) { + map.removeOverlay(data.tails.remove(unusedTailCompetitorDAO)); + } + } + } + + + private String getBoatImageURL(GPSFixDAO boatFix, boolean highlighted) { + return getBoatImageURL(getBoatImageRotator(boatFix, highlighted), boatFix); + } + + private String getBoatImageURL(ImageRotator boatImageRotator, GPSFixDAO boatFix) { + // the possible zoom level range is 0 to 21 (zoom level 0 would show the whole world) + int zoomLevel = map.getZoomLevel(); + double minScaleFactor = 0.1; + double maxScaleFactor = 1.0; + + double scaleDiffPerZoomLevel = (maxScaleFactor - minScaleFactor) / 21.0; + + double scaleFactor = minScaleFactor + scaleDiffPerZoomLevel * zoomLevel; + + return boatImageRotator.getRotatedImageURL(boatFix.speedWithBearing.bearingInDegrees, scaleFactor); + } + + private Icon getBoatImageIcon(GPSFixDAO boatFix, boolean highlighted) { + ImageRotator boatImageRotator = getBoatImageRotator(boatFix, highlighted); + Icon icon = Icon.newInstance(getBoatImageURL(boatImageRotator, boatFix)); + icon.setIconAnchor(boatImageRotator.getAnchor()); + return icon; + } + + private ImageRotator getBoatImageRotator(GPSFixDAO boatFix, boolean highlighted) { + if (boatFix.tack == Tack.PORT) { + if (LegType.DOWNWIND == boatFix.legType) { + if (highlighted) { + return boatIconHighlightedDownwindStarboardRotator; + } else { + return boatIconDownwindStarboardRotator; + } + } else { + if (highlighted) { + return boatIconHighlightedStarboardRotator; + } else { + return boatIconStarboardRotator; + } + } + } else { + if (LegType.DOWNWIND == boatFix.legType) { + if (highlighted) { + return boatIconHighlightedDownwindPortRotator; + } else { + return boatIconDownwindPortRotator; + } + } else { + if (highlighted) { + return boatIconHighlightedPortRotator; + } else { + return boatIconPortRotator; + } + } + } + } + + protected Marker createBuoyMarker(final MarkDAO markDAO) { + MarkerOptions options = MarkerOptions.newInstance(); + if (buoyIcon != null) { + options.setIcon(buoyIcon); + } + options.setTitle(markDAO.name); + final Marker buoyMarker = new Marker(LatLng.newInstance(markDAO.position.latDeg, markDAO.position.lngDeg), + options); + buoyMarker.addMarkerClickHandler(new MarkerClickHandler() { + @Override + public void onClick(MarkerClickEvent event) { + LatLng latlng = buoyMarker.getLatLng(); + showMarkInfoWindow(markDAO, latlng); + } + }); + return buoyMarker; + } + + protected Marker createBoatMarker(final CompetitorDAO competitorDAO, boolean highlighted) { + GPSFixDAO boatFix = data.getBoatFix(competitorDAO); + double latDeg = boatFix.position.latDeg; + double lngDeg = boatFix.position.lngDeg; + MarkerOptions options = MarkerOptions.newInstance(); + Icon icon = getBoatImageIcon(boatFix, highlighted); + options.setIcon(icon); + options.setTitle(competitorDAO.name); + final Marker boatMarker = new Marker(LatLng.newInstance(latDeg, lngDeg), options); + boatMarker.addMarkerClickHandler(new MarkerClickHandler() { + @Override + public void onClick(MarkerClickEvent event) { + LatLng latlng = boatMarker.getLatLng(); + showCompetitorInfoWindow(competitorDAO, latlng); + } + }); + boatMarker.addMarkerMouseOverHandler(new MarkerMouseOverHandler() { + @Override + public void onMouseOver(MarkerMouseOverEvent event) { + map.setTitle(competitorDAO.name); + // setSelectedInMap(competitorDAO, true); + // quickRanksBox.setItemSelected(quickRanksList.indexOf(competitorDAO), true); + } + }); + boatMarker.addMarkerMouseOutHandler(new MarkerMouseOutHandler() { + @Override + public void onMouseOut(MarkerMouseOutEvent event) { + map.setTitle(""); + } + }); + return boatMarker; + } + + private void showMarkInfoWindow(MarkDAO markDAO, LatLng latlng) { + map.getInfoWindow().open(latlng, new InfoWindowContent(getInfoWindowContent(markDAO))); + } + + private void showCompetitorInfoWindow(final CompetitorDAO competitorDAO, LatLng where) { + GPSFixDAO latestFixForCompetitor = data.getBoatFix(competitorDAO); + map.getInfoWindow().open(where, + new InfoWindowContent(getInfoWindowContent(competitorDAO, latestFixForCompetitor))); + } + + private Widget getInfoWindowContent(MarkDAO markDAO) { + VerticalPanel result = new VerticalPanel(); + result.add(new Label("Mark " + markDAO.name)); + result.add(new Label("" + markDAO.position)); + return result; + } + + private Widget getInfoWindowContent(CompetitorDAO competitorDAO, GPSFixDAO lastFix) { + final VerticalPanel result = new VerticalPanel(); + result.add(new Label("Competitor " + competitorDAO.name)); + result.add(new Label("" + lastFix.position)); + result.add(new Label(lastFix.speedWithBearing.speedInKnots + "kts " + lastFix.speedWithBearing.bearingInDegrees + + "deg")); + result.add(new Label("Tack: " + lastFix.tack.name())); + if (!selectedEventAndRace.isEmpty()) { + EventDAO event = selectedEventAndRace.get(selectedEventAndRace.size() - 1).getA(); + RaceDAO race = selectedEventAndRace.get(selectedEventAndRace.size() - 1).getC(); + if (event != null && race != null) { + Map from = new HashMap(); + from.put(competitorDAO, data.fixes.get(competitorDAO).get(data.firstShownFix.get(competitorDAO)).timepoint); + Map to = new HashMap(); + to.put(competitorDAO, data.getBoatFix(competitorDAO).timepoint); + /* currently not showing Douglas-Peucker points; TODO use checkboxes to select what to show (Bug #6) */ + sailingService.getDouglasPoints(new EventNameAndRaceName(event.name, race.name), from, to, 3, + new AsyncCallback>>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error obtaining douglas positions: " + caught.getMessage()); + } + + @Override + public void onSuccess(Map> result) { + data.lastDouglasPeuckerResult = result; + if (data.douglasMarkers != null) { + removeAllMarkDouglasPeuckerpoints(); + } + if (!timer.isPlaying()) { + if (showDouglasPeuckerPoints) { + showMarkDouglasPeuckerPoints(result); + } + } + } + }); + sailingService.getManeuvers(new EventNameAndRaceName(event.name, race.name), from, to, + new AsyncCallback>>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error obtaining maneuvers: " + caught.getMessage()); + } + + @Override + public void onSuccess(Map> result) { + data.lastManeuverResult = result; + if (data.maneuverMarkers != null) { + removeAllManeuverMarkers(); + } + if (!timer.isPlaying()) { + showManeuvers(result); + } + } + }); + + } + } + return result; + } + + private String getColorString(CompetitorDAO competitorDAO) { + // TODO green no more than 70, red no less than 120 + return "#" + (Integer.toHexString(competitorDAO.hashCode()) + "000000").substring(0, 4).toUpperCase() + "00"; + } + + /** + * Creates a polyline for the competitor represented by competitorDAO, taking the raceMapData.fixes from + * {@link #data.fixes raceMapData.fixes.get(competitorDAO)} and using the raceMapData.fixes starting at time point from (inclusive) + * up to the last fix with time point before to. The polyline is returned. Updates are applied to + * {@link #data.lastShownFix}, {@link #data.firstShownFix} and {@link #data.tails}. + */ + protected Polyline createTailAndUpdateIndices(final CompetitorDAO competitorDAO, Date from, Date to) { + List points = new ArrayList(); + List fixesForCompetitor = data.fixes.get(competitorDAO); + int indexOfFirst = -1; + int indexOfLast = -1; + int i = 0; + for (Iterator fixIter = fixesForCompetitor.iterator(); fixIter.hasNext() && indexOfLast == -1;) { + GPSFixDAO fix = fixIter.next(); + if (!fix.timepoint.before(to)) { + indexOfLast = i; + } else { + LatLng point = null; + if (indexOfFirst == -1) { + if (!fix.timepoint.before(from)) { + indexOfFirst = i; + point = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg); + } + } else { + point = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg); + } + if (point != null) { + points.add(point); + } + } + i++; + } + if (indexOfLast == -1) { + indexOfLast = i - 1; + } + if (indexOfFirst != -1 && indexOfLast != -1) { + data.firstShownFix.put(competitorDAO, indexOfFirst); + data.lastShownFix.put(competitorDAO, indexOfLast); + } + PolylineOptions options = PolylineOptions.newInstance( + /* clickable */true, /* geodesic */true); + Polyline result = new Polyline(points.toArray(new LatLng[0]), getColorString(competitorDAO), /* width */3, + /* opacity */0.5, options); + result.addPolylineClickHandler(new PolylineClickHandler() { + @Override + public void onClick(PolylineClickEvent event) { + showCompetitorInfoWindow(competitorDAO, lastMousePosition); + } + }); + result.addPolylineMouseOverHandler(new PolylineMouseOverHandler() { + @Override + public void onMouseOver(PolylineMouseOverEvent event) { + map.setTitle(competitorDAO.name); + } + }); + result.addPolylineMouseOutHandler(new PolylineMouseOutHandler() { + @Override + public void onMouseOut(PolylineMouseOutEvent event) { + map.setTitle(""); + } + }); + data.tails.put(competitorDAO, result); + return result; + } + + protected void removeAllMarkDouglasPeuckerpoints() { + if (data.douglasMarkers != null) { + for (Marker marker : data.douglasMarkers) { + map.removeOverlay(marker); + } + } + data.douglasMarkers = null; + } + + protected void removeAllManeuverMarkers() { + if (data.maneuverMarkers != null) { + for (Marker marker : data.maneuverMarkers) { + map.removeOverlay(marker); + } + data.maneuverMarkers = null; + } + } + + protected void showMarkDouglasPeuckerPoints(Map> gpsFixPointMapForCompetitors) { + data.douglasMarkers = new HashSet(); + if (map != null && gpsFixPointMapForCompetitors != null) { + Set keySet = gpsFixPointMapForCompetitors.keySet(); + Iterator iter = keySet.iterator(); + while (iter.hasNext()) { + CompetitorDAO competitorDAO = iter.next(); + List gpsFix = gpsFixPointMapForCompetitors.get(competitorDAO); + for (GPSFixDAO fix : gpsFix) { + LatLng latLng = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg); + MarkerOptions options = MarkerOptions.newInstance(); + options.setTitle(fix.timepoint+": "+fix.position+", "+fix.speedWithBearing.toString()); + Marker marker = new Marker(latLng, options); + data.douglasMarkers.add(marker); + map.addOverlay(marker); + } + } + } + } + + protected void showManeuvers(Map> maneuvers) { + data.maneuverMarkers = new HashSet(); + if (map != null && maneuvers != null) { + Set keySet = maneuvers.keySet(); + Iterator iter = keySet.iterator(); + while (iter.hasNext()) { + CompetitorDAO competitorDAO = iter.next(); + List maneuversForCompetitor = maneuvers.get(competitorDAO); + for (ManeuverDAO maneuver : maneuversForCompetitor) { + boolean showThisManeuver = true; + LatLng latLng = LatLng.newInstance(maneuver.position.latDeg, maneuver.position.lngDeg); + MarkerOptions options = MarkerOptions.newInstance(); + options.setTitle("" + maneuver.timepoint + ": " + maneuver.type + " " + + maneuver.directionChangeInDegrees + "deg from " + maneuver.speedWithBearingBefore + + " to " + maneuver.speedWithBearingAfter); + if (maneuver.type.equals("TACK") && showManeuverTack) { + if (maneuver.newTack.equals("PORT")) { + options.setIcon(tackToPortIcon); + } else { + options.setIcon(tackToStarboardIcon); + } + } else if (maneuver.type.equals("JIBE") && showManeuverJibe) { + if (maneuver.newTack.equals("PORT")) { + options.setIcon(jibeToPortIcon); + } else { + options.setIcon(jibeToStarboardIcon); + } + } else if (maneuver.type.equals("HEAD_UP") && showManeuverHeadUp) { + if (maneuver.newTack.equals("PORT")) { + options.setIcon(headUpOnPortIcon); + } else { + options.setIcon(headUpOnStarboardIcon); + } + } else if (maneuver.type.equals("BEAR_AWAY") && showManeuverBearAway) { + if (maneuver.newTack.equals("PORT")) { + options.setIcon(bearAwayOnPortIcon); + } else { + options.setIcon(bearAwayOnStarboardIcon); + } + } else if (maneuver.type.equals("PENALTY_CIRCLE") && showManeuverPenaltyCircle) { + if (maneuver.newTack.equals("PORT")) { + options.setIcon(penaltyCircleToPortIcon); + } else { + options.setIcon(penaltyCircleToStarboardIcon); + } + } else if (maneuver.type.equals("MARK_PASSING") && showManeuverMarkPassing) { + if (maneuver.newTack.equals("PORT")) { + options.setIcon(markPassingToPortIcon); + } else { + options.setIcon(markPassingToStarboardIcon); + } + } else { + if (maneuver.type.equals("UNKNOWN") && showManeuverOther) { + options.setIcon(unknownManeuverIcon); + } else { + showThisManeuver = false; + } + } + if (showThisManeuver) { + Marker marker = new Marker(latLng, options); + data.maneuverMarkers.add(marker); + map.addOverlay(marker); + } + } + } + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapData.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapData.java new file mode 100644 index 00000000000..572b7f12476 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapData.java @@ -0,0 +1,197 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.maps.client.geom.LatLng; +import com.google.gwt.maps.client.overlay.Marker; +import com.google.gwt.maps.client.overlay.Polyline; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.shared.CompetitorDAO; +import com.sap.sailing.gwt.ui.shared.GPSFixDAO; +import com.sap.sailing.gwt.ui.shared.ManeuverDAO; +import com.sap.sailing.gwt.ui.shared.MarkDAO; + +public class RaceMapData { + + /** + * Tails of competitors currently displayed as overlays on the map. + */ + protected final Map tails; + + /** + * Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the first fix shown + * in {@link #tails} is . + */ + protected final Map firstShownFix; + + /** + * Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the last fix shown in + * {@link #tails} is . + */ + protected final Map lastShownFix; + + /** + * Fixes of each competitors tail. If a list is contained for a competitor, the list contains a timely "contiguous" + * list of fixes for the competitor. This means the server has no more data for the time interval covered, unless + * the last fix was {@link GPSFixDAO#extrapolated obtained by extrapolation}. + */ + protected final Map> fixes; + + /** + * Markers used as boat display on the map + */ + protected final Map boatMarkers; + + protected final Map buoyMarkers; + + /** + * markers displayed in response to + * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)} + */ + protected Set douglasMarkers; + + /** + * markers displayed in response to + * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)} + */ + protected Set maneuverMarkers; + + protected Map> lastManeuverResult; + + protected Map> lastDouglasPeuckerResult; + + public RaceMapData() { + tails = new HashMap(); + firstShownFix = new HashMap(); + lastShownFix = new HashMap(); + buoyMarkers = new HashMap(); + boatMarkers = new HashMap(); + fixes = new HashMap>(); + } + + protected Date getTimepointOfFirstNonExtrapolated(List fixesForCompetitor) { + for (GPSFixDAO fix : fixesForCompetitor) { + if (!fix.extrapolated) { + return fix.timepoint; + } + } + return null; + } + + protected Date getTimepointOfLastNonExtrapolated(List fixesForCompetitor) { + if (!fixesForCompetitor.isEmpty()) { + for (ListIterator fixIter = fixesForCompetitor.listIterator(fixesForCompetitor.size() - 1); fixIter + .hasPrevious();) { + GPSFixDAO fix = fixIter.previous(); + if (!fix.extrapolated) { + return fix.timepoint; + } + } + } + return null; + } + + /** + * While updating the {@link #fixes} for competitorDAO, the invariants for {@link #tails} and + * {@link #firstShownFix} and {@link #lastShownFix} are maintained: each time a fix is inserted, the + * {@link #firstShownFix}/{@link #lastShownFix} records for competitorDAO are incremented if they are + * greater or equal to the insertion index and we have a tail in {@link #tails} for competitorDAO. + * Additionally, if the fix is in between the fixes shown in the competitor's tail, the tail is adjusted by + * inserting the corresponding fix. + */ + protected void mergeFixes(CompetitorDAO competitorDAO, List mergeThis) { + List intoThis = fixes.get(competitorDAO); + int indexOfFirstShownFix = firstShownFix.get(competitorDAO) == null ? -1 : firstShownFix.get(competitorDAO); + int indexOfLastShownFix = lastShownFix.get(competitorDAO) == null ? -1 : lastShownFix.get(competitorDAO); + Polyline tail = tails.get(competitorDAO); + int intoThisIndex = 0; + for (GPSFixDAO mergeThisFix : mergeThis) { + while (intoThisIndex < intoThis.size() + && intoThis.get(intoThisIndex).timepoint.before(mergeThisFix.timepoint)) { + intoThisIndex++; + } + if (intoThisIndex < intoThis.size() && intoThis.get(intoThisIndex).timepoint.equals(mergeThisFix.timepoint)) { + // exactly same time point; replace with fix from mergeThis + intoThis.set(intoThisIndex, mergeThisFix); + } else { + intoThis.add(intoThisIndex, mergeThisFix); + if (indexOfFirstShownFix >= intoThisIndex) { + indexOfFirstShownFix++; + } + if (indexOfLastShownFix >= intoThisIndex) { + indexOfLastShownFix++; + } + if (tail != null && intoThisIndex >= indexOfFirstShownFix && intoThisIndex <= indexOfLastShownFix) { + tail.insertVertex(intoThisIndex - indexOfFirstShownFix, + LatLng.newInstance(mergeThisFix.position.latDeg, mergeThisFix.position.lngDeg)); + } + } + intoThisIndex++; + } + } + + protected GPSFixDAO getBoatFix(CompetitorDAO competitorDAO) { + return fixes.get(competitorDAO).get(lastShownFix.get(competitorDAO)); + } + + /** + * If the tail starts before from, removes leading vertices from tail that are before + * from. This is determined by using the {@link #firstShownFix} index which tells us where in + * {@link #fixes} we find the sequence of fixes currently represented in the tail. + *

+ * + * If the tail starts after from, vertices for those {@link #fixes} for competitorDAO at + * or after time point from and before the time point of the first fix displayed so far in the tail and + * before to are prepended to the tail. + *

+ * + * Now to the end of the tail: if the existing tail's end exceeds to, the vertices in excess are + * removed (aided by {@link #lastShownFix}). Otherwise, for the competitor's fixes starting at the tail's end up to + * to are appended to the tail. + *

+ * + * When this method returns, {@link #firstShownFix} and {@link #lastShownFix} have been updated accordingly. + */ + protected void updateTail(Polyline tail, CompetitorDAO competitorDAO, Date from, Date to) { + List fixesForCompetitor = fixes.get(competitorDAO); + int indexOfFirstShownFix = firstShownFix.get(competitorDAO) == null ? -1 : firstShownFix.get(competitorDAO); + while (indexOfFirstShownFix != -1 && tail.getVertexCount() > 0 + && fixesForCompetitor.get(indexOfFirstShownFix).timepoint.before(from)) { + tail.deleteVertex(0); + indexOfFirstShownFix++; + } + // now the polyline contains no more vertices representing fixes before "from"; + // go back in time starting at indexOfFirstShownFix while the fixes are still at or after "from" + // and insert corresponding vertices into the polyline + while (indexOfFirstShownFix > 0 && !fixesForCompetitor.get(indexOfFirstShownFix - 1).timepoint.before(from)) { + indexOfFirstShownFix--; + GPSFixDAO fix = fixesForCompetitor.get(indexOfFirstShownFix); + tail.insertVertex(0, LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg)); + } + // now adjust the polylines tail: remove excess vertices that are after "to" + int indexOfLastShownFix = lastShownFix.get(competitorDAO) == null ? -1 : lastShownFix.get(competitorDAO); + while (indexOfLastShownFix != -1 && tail.getVertexCount() > 0 + && fixesForCompetitor.get(indexOfLastShownFix).timepoint.after(to)) { + tail.deleteVertex(tail.getVertexCount() - 1); + indexOfLastShownFix--; + } + // now the polyline contains no more vertices representing fixes after "to"; + // go forward in time starting at indexOfLastShownFix while the fixes are still at or before "to" + // and insert corresponding vertices into the polyline + while (indexOfLastShownFix < fixesForCompetitor.size() - 1 + && !fixesForCompetitor.get(indexOfLastShownFix + 1).timepoint.after(to)) { + indexOfLastShownFix++; + GPSFixDAO fix = fixesForCompetitor.get(indexOfLastShownFix); + tail.insertVertex(tail.getVertexCount(), LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg)); + } + firstShownFix.put(competitorDAO, indexOfFirstShownFix); + lastShownFix.put(competitorDAO, indexOfLastShownFix); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapPanel.java index 38129369515..f695974a089 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceMapPanel.java @@ -3,44 +3,19 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; import java.util.Collection; import java.util.Date; -import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Set; -import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.maps.client.InfoWindowContent; -import com.google.gwt.maps.client.MapWidget; -import com.google.gwt.maps.client.Maps; -import com.google.gwt.maps.client.control.LargeMapControl3D; -import com.google.gwt.maps.client.control.MenuMapTypeControl; -import com.google.gwt.maps.client.control.ScaleControl; -import com.google.gwt.maps.client.event.MapDragEndHandler; -import com.google.gwt.maps.client.event.MapMouseMoveHandler; -import com.google.gwt.maps.client.event.MapZoomEndHandler; -import com.google.gwt.maps.client.event.MarkerClickHandler; -import com.google.gwt.maps.client.event.MarkerMouseOutHandler; -import com.google.gwt.maps.client.event.MarkerMouseOverHandler; -import com.google.gwt.maps.client.event.PolylineClickHandler; -import com.google.gwt.maps.client.event.PolylineMouseOutHandler; -import com.google.gwt.maps.client.event.PolylineMouseOverHandler; import com.google.gwt.maps.client.geom.LatLng; -import com.google.gwt.maps.client.geom.LatLngBounds; -import com.google.gwt.maps.client.geom.Point; -import com.google.gwt.maps.client.overlay.Icon; import com.google.gwt.maps.client.overlay.Marker; -import com.google.gwt.maps.client.overlay.MarkerOptions; -import com.google.gwt.maps.client.overlay.Polyline; -import com.google.gwt.maps.client.overlay.PolylineOptions; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.FormPanel; @@ -53,8 +28,6 @@ import com.google.gwt.user.client.ui.ProvidesResize; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.Util.Pair; import com.sap.sailing.domain.common.Util.Triple; import com.sap.sailing.gwt.ui.client.ErrorReporter; @@ -80,138 +53,20 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe private final SailingServiceAsync sailingService; private final ErrorReporter errorReporter; private final Grid grid; - private MapWidget map; private final RacesListBoxPanel newRaceListBox; private final ListBox quickRanksBox; private final List quickRanksList; private final TimePanel timePanel; - /** - * Two sails on downwind leg, wind from port (sails on starboard); no highlighting - */ - private ImageRotator boatIconDownwindPortRotator; - - /** - * Two sails on downwind leg, wind from port (sails on starboard); with highlighting - */ - private ImageRotator boatIconHighlightedDownwindPortRotator; - - /** - * Two sails on downwind leg, wind from starboard (sails on port); no highlighting - */ - private ImageRotator boatIconDownwindStarboardRotator; - - /** - * Two sails on downwind leg, wind from starboard (sails on port); with highlighting - */ - private ImageRotator boatIconHighlightedDownwindStarboardRotator; - - /** - * One sail, wind from port (sails on starboard); no highlighting - */ - private ImageRotator boatIconPortRotator; - - /** - * One sail, wind from port (sails on starboard); with highlighting - */ - private ImageRotator boatIconHighlightedPortRotator; - - /** - * One sail, wind from starboard (sails on port); no highlighting - */ - private ImageRotator boatIconStarboardRotator; - - /** - * One sail, wind from starboard (sails on port); with highlighting - */ - private ImageRotator boatIconHighlightedStarboardRotator; - - private Icon buoyIcon; - private Icon tackToStarboardIcon; - private Icon tackToPortIcon; - private Icon jibeToStarboardIcon; - private Icon jibeToPortIcon; - private Icon markPassingToStarboardIcon; - private Icon markPassingToPortIcon; - private Icon headUpOnStarboardIcon; - private Icon headUpOnPortIcon; - private Icon bearAwayOnStarboardIcon; - private Icon bearAwayOnPortIcon; - private Icon unknownManeuverIcon; - private Icon penaltyCircleToStarboardIcon; - private Icon penaltyCircleToPortIcon; - private LatLng lastMousePosition; private final Set competitorsSelectedInMap; private final Timer timer; private List> checkboxAndType; private CheckBox checkBoxDouglasPeuckerPoints; - private long TAILLENGTHINMILLISECONDS = 30000l; - - /** - * If the user explicitly zoomed or panned the map, don't adjust zoom/pan unless a new race is selected - */ - private boolean mapZoomedOrPannedSinceLastRaceSelectionChange = false; - - /** - * Used to check if the first initial zoom to the buoy markers was already done. - */ - private boolean mapFirstZoomDone = false; - - /** - * Tails of competitors currently displayed as overlays on the map. - */ - private final Map tails; - - /** - * Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the first fix shown - * in {@link #tails} is . - */ - private final Map firstShownFix; - - /** - * Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the last fix shown in - * {@link #tails} is . - */ - private final Map lastShownFix; - - /** - * Fixes of each competitors tail. If a list is contained for a competitor, the list contains a timely "contiguous" - * list of fixes for the competitor. This means the server has no more data for the time interval covered, unless - * the last fix was {@link GPSFixDAO#extrapolated obtained by extrapolation}. - */ - private final Map> fixes; - - /** - * Markers used as boat display on the map - */ - private final Map boatMarkers; - - private final Map buoyMarkers; - - /** - * markers displayed in response to - * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)} - */ - private Set douglasMarkers; - - /** - * markers displayed in response to - * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)} - */ - private Set maneuverMarkers; - - // key for domain web4sap.com - private final String mapsAPIKey = "ABQIAAAAmvjPh3ZpHbnwuX3a66lDqRRLCigyC_gRDASMpyomD2do5awpNhRCyD_q-27hwxKe_T6ivSZ_0NgbUg"; - private final CheckBox showOnlySelected; private final IntegerBox tailLengthBox; - protected Map> lastManeuverResult; - - protected Map> lastDouglasPeuckerResult; - /** * RPC calls may receive responses out of order if there are multiple calls in-flight at the same time. If the time * slider is moved quickly it generates many requests for boat positions quickly after each other. Sometimes, @@ -228,7 +83,7 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe */ private int startedProcessingRequestID; - private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + private final RaceMap raceMap; public RaceMapPanel(SailingServiceAsync sailingService, ErrorReporter errorReporter, final EventRefresher eventRefresher, StringConstants stringConstants) { @@ -236,11 +91,6 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe this.errorReporter = errorReporter; this.timer = new Timer(/* delayBetweenAutoAdvancesInMilliseconds */500); competitorsSelectedInMap = new HashSet(); - tails = new HashMap(); - firstShownFix = new HashMap(); - lastShownFix = new HashMap(); - buoyMarkers = new HashMap(); - boatMarkers = new HashMap(); checkboxAndType = new ArrayList>(); final VerticalPanel verticalCheckBoxPanel = new VerticalPanel(); verticalCheckBoxPanel.add(new Label(stringConstants.maneuverTypes())); @@ -259,46 +109,54 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe checkBoxMarkPassing.setValue(true); checkboxAndType.add(new Pair(checkBoxMarkPassing, "MARK_PASSING")); checkboxAndType.add(new Pair(new CheckBox(stringConstants.otherManeuver()), "OTHER")); + for (Pair pair : checkboxAndType) { pair.getA().addValueChangeHandler(new ValueChangeHandler() { @Override public void onValueChange(ValueChangeEvent event) { - if (!timer.isPlaying() && lastManeuverResult != null) { - removeAllManeuverMarkers(); - showManeuvers(lastManeuverResult); + if (!timer.isPlaying() && raceMap.data.lastManeuverResult != null) { + raceMap.removeAllManeuverMarkers(); + showManeuvers(raceMap.data.lastManeuverResult); } } }); verticalCheckBoxPanel.add(pair.getA()); } + checkBoxDouglasPeuckerPoints = new CheckBox(stringConstants.douglasPeuckerPoints()); checkBoxDouglasPeuckerPoints.addValueChangeHandler(new ValueChangeHandler() { @Override public void onValueChange(ValueChangeEvent event) { - if (!timer.isPlaying() && lastDouglasPeuckerResult != null && event.getValue()) { - removeAllMarkDouglasPeuckerpoints(); - showMarkDouglasPeuckerPoints(lastDouglasPeuckerResult); + if (!timer.isPlaying() && raceMap.data.lastDouglasPeuckerResult != null && event.getValue()) { + raceMap.showDouglasPeuckerPoints = true; + raceMap.removeAllMarkDouglasPeuckerpoints(); + raceMap.showMarkDouglasPeuckerPoints(raceMap.data.lastDouglasPeuckerResult); } else if (!event.getValue()) { - removeAllMarkDouglasPeuckerpoints(); + raceMap.showDouglasPeuckerPoints = false; + raceMap.removeAllMarkDouglasPeuckerpoints(); } } }); verticalCheckBoxPanel.add(checkBoxDouglasPeuckerPoints); - fixes = new HashMap>(); this.grid = new Grid(3, 2); setWidget(grid); grid.setSize("100%", "100%"); grid.getColumnFormatter().setWidth(0, "20%"); grid.getColumnFormatter().setWidth(1, "80%"); grid.getCellFormatter().setHeight(2, 1, "100%"); - loadMapsAPI(); + + raceMap = new RaceMap(sailingService, errorReporter, timer); + raceMap.loadMapsAPI(grid, 2, 1); + + setMapDisplayOptions(); + newRaceListBox = new RacesListBoxPanel(eventRefresher, stringConstants); newRaceListBox.addRaceSelectionChangeListener(this); grid.setWidget(0, 0, newRaceListBox); PositionDAO pos = new PositionDAO(); - if (!boatMarkers.isEmpty()) { - LatLng latLng = boatMarkers.values().iterator().next().getLatLng(); + if (!raceMap.data.boatMarkers.isEmpty()) { + LatLng latLng = raceMap.data.boatMarkers.values().iterator().next().getLatLng(); pos.latDeg = latLng.getLatitude(); pos.lngDeg = latLng.getLongitude(); } @@ -313,11 +171,11 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe HorizontalPanel labelAndTailLengthBox = new HorizontalPanel(); labelAndTailLengthBox.add(new Label(stringConstants.tailLength())); tailLengthBox = new IntegerBox(); - tailLengthBox.setValue((int) (TAILLENGTHINMILLISECONDS / 1000)); + tailLengthBox.setValue((int) (raceMap.tailLengthInMilliSeconds / 1000)); tailLengthBox.addValueChangeHandler(new ValueChangeHandler() { @Override public void onValueChange(ValueChangeEvent event) { - TAILLENGTHINMILLISECONDS = 1000l * event.getValue(); + raceMap.tailLengthInMilliSeconds = 1000l * event.getValue(); refreshMapContents(); } }); @@ -381,21 +239,21 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe private void setSelectedInMap(CompetitorDAO competitorDAO, boolean itemSelected) { if (!itemSelected && competitorsSelectedInMap.contains(competitorDAO)) { // "lowlight" currently selected competitor - Marker highlightedMarker = boatMarkers.get(competitorDAO); + Marker highlightedMarker = raceMap.data.boatMarkers.get(competitorDAO); if (highlightedMarker != null) { - Marker lowlightedMarker = createBoatMarker(competitorDAO, false); - map.removeOverlay(highlightedMarker); - map.addOverlay(lowlightedMarker); - boatMarkers.put(competitorDAO, lowlightedMarker); + Marker lowlightedMarker = raceMap.createBoatMarker(competitorDAO, false); + raceMap.map.removeOverlay(highlightedMarker); + raceMap.map.addOverlay(lowlightedMarker); + raceMap.data.boatMarkers.put(competitorDAO, lowlightedMarker); competitorsSelectedInMap.remove(competitorDAO); } } else if (itemSelected && !competitorsSelectedInMap.contains(competitorDAO)) { - Marker lowlightedMarker = boatMarkers.get(competitorDAO); + Marker lowlightedMarker = raceMap.data.boatMarkers.get(competitorDAO); if (lowlightedMarker != null) { - Marker highlightedMarker = createBoatMarker(competitorDAO, true); - map.removeOverlay(lowlightedMarker); - map.addOverlay(highlightedMarker); - boatMarkers.put(competitorDAO, highlightedMarker); + Marker highlightedMarker = raceMap.createBoatMarker(competitorDAO, true); + raceMap.map.removeOverlay(lowlightedMarker); + raceMap.map.addOverlay(highlightedMarker); + raceMap.data.boatMarkers.put(competitorDAO, highlightedMarker); int selectionIndex = quickRanksList.indexOf(competitorDAO); quickRanksBox.setItemSelected(selectionIndex, true); } @@ -404,99 +262,16 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe } } - private void loadMapsAPI() { - Maps.loadMapsApi(mapsAPIKey, "2", false, new Runnable() { - public void run() { - map = new MapWidget(); - map.addControl(new LargeMapControl3D()); - map.addControl(new MenuMapTypeControl()); - map.addControl(new ScaleControl()); - // Add the map to the HTML host page - grid.setWidget(2, 1, map); - map.setSize("100%", "100%"); - map.setScrollWheelZoomEnabled(true); - map.setContinuousZoom(true); - map.addMapZoomEndHandler(new MapZoomEndHandler() { - @Override - public void onZoomEnd(MapZoomEndEvent event) { - mapZoomedOrPannedSinceLastRaceSelectionChange = true; - } - }); - map.addMapDragEndHandler(new MapDragEndHandler() { - @Override - public void onDragEnd(MapDragEndEvent event) { - mapZoomedOrPannedSinceLastRaceSelectionChange = true; - } - }); - map.addMapMouseMoveHandler(new MapMouseMoveHandler() { - @Override - public void onMouseMove(MapMouseMoveEvent event) { - lastMousePosition = event.getLatLng(); - } - }); - boatIconDownwindPortRotator = new ImageRotator(resources.lowlightedBoatIconDW_Port()); - boatIconHighlightedDownwindPortRotator = new ImageRotator(resources.highlightedBoatIconDW_Port()); - boatIconDownwindStarboardRotator = new ImageRotator(resources.lowlightedBoatIconDW_Starboard()); - boatIconHighlightedDownwindStarboardRotator = new ImageRotator(resources - .highlightedBoatIconDW_Starboard()); - boatIconPortRotator = new ImageRotator(resources.lowlightedBoatIcon_Port()); - boatIconHighlightedPortRotator = new ImageRotator(resources.highlightedBoatIcon_Port()); - boatIconStarboardRotator = new ImageRotator(resources.lowlightedBoatIcon_Starboard()); - boatIconHighlightedStarboardRotator = new ImageRotator(resources.highlightedBoatIcon_Starboard()); - buoyIcon = Icon.newInstance(resources.buoyIcon().getSafeUri().asString()); - buoyIcon.setIconAnchor(Point.newInstance(4, 4)); - tackToStarboardIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|00FF00|000000"); - tackToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); - tackToPortIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|FF0000|000000"); - tackToPortIcon.setIconAnchor(Point.newInstance(10, 33)); - jibeToStarboardIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=J|00FF00|000000"); - jibeToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); - jibeToPortIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=J|FF0000|000000"); - jibeToPortIcon.setIconAnchor(Point.newInstance(10, 33)); - headUpOnStarboardIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=H|00FF00|000000"); - headUpOnStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); - headUpOnPortIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=H|FF0000|000000"); - headUpOnPortIcon.setIconAnchor(Point.newInstance(10, 33)); - bearAwayOnStarboardIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=B|00FF00|000000"); - bearAwayOnStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); - bearAwayOnPortIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=B|FF0000|000000"); - bearAwayOnPortIcon.setIconAnchor(Point.newInstance(10, 33)); - markPassingToStarboardIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=M|00FF00|000000"); - markPassingToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); - markPassingToPortIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=M|FF0000|000000"); - markPassingToPortIcon.setIconAnchor(Point.newInstance(10, 33)); - unknownManeuverIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=?|FFFFFF|000000"); - unknownManeuverIcon.setIconAnchor(Point.newInstance(10, 33)); - penaltyCircleToStarboardIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|00FF00|000000"); - penaltyCircleToStarboardIcon.setIconAnchor(Point.newInstance(10, 33)); - penaltyCircleToPortIcon = Icon - .newInstance("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|FF0000|000000"); - penaltyCircleToPortIcon.setIconAnchor(Point.newInstance(10, 33)); - } - }); - } - @Override public void fillEvents(List result) { newRaceListBox.fillEvents(result); + raceMap.selectedEventAndRace = newRaceListBox.getSelectedEventAndRace(); } @Override public void onRaceSelectionChange(List> selectedRaces) { - mapFirstZoomDone = false; - mapZoomedOrPannedSinceLastRaceSelectionChange = false; + raceMap.mapFirstZoomDone = false; + raceMap.mapZoomedOrPannedSinceLastRaceSelectionChange = false; if (!selectedRaces.isEmpty() && selectedRaces.get(selectedRaces.size() - 1) != null) { RaceDAO raceDAO = selectedRaces.get(selectedRaces.size() - 1).getC(); if (raceDAO.startOfRace != null) { @@ -530,7 +305,7 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe EventDAO event = selection.get(selection.size() - 1).getA(); RaceDAO race = selection.get(selection.size() - 1).getC(); if (event != null && race != null) { - final Triple, Map, Map> fromAndToAndOverlap = computeFromAndTo(date); + final Triple, Map, Map> fromAndToAndOverlap = raceMap.computeFromAndTo(date,this.getCompetitorsToShow()); final int requestID = boatPositionRequestIDCounter++; sailingService.getBoatPositions(new EventNameAndRaceName(event.name, race.name), fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), true, @@ -545,14 +320,14 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe // process response only if not received out of order if (startedProcessingRequestID < requestID) { startedProcessingRequestID = requestID; - Date from = new Date(date.getTime() - TAILLENGTHINMILLISECONDS); - updateFixes(result, fromAndToAndOverlap.getC()); - showBoatsOnMap(from, date); - if (douglasMarkers != null) { - removeAllMarkDouglasPeuckerpoints(); + Date from = new Date(date.getTime() - raceMap.tailLengthInMilliSeconds); + raceMap.updateFixes(result, fromAndToAndOverlap.getC()); + raceMap.showBoatsOnMap(from, date, getCompetitorsToShow(), competitorsSelectedInMap); + if (raceMap.data.douglasMarkers != null) { + raceMap.removeAllMarkDouglasPeuckerpoints(); } - if (maneuverMarkers != null) { - removeAllManeuverMarkers(); + if (raceMap.data.maneuverMarkers != null) { + raceMap.removeAllManeuverMarkers(); } } } @@ -567,7 +342,7 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe @Override public void onSuccess(List result) { - showMarksOnMap(result); + raceMap.showMarksOnMap(result); } }); sailingService.getQuickRanks(new EventNameAndRaceName(event.name, race.name), date, @@ -587,157 +362,6 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe } } - /** - * From {@link #fixes} as well as the selection of {@link #getCompetitorsToShow competitors to show}, computes the - * from/to times for which to request GPS fixes from the server. No update is performed here to {@link #fixes}. The - * result guarantees that, when used in - * {@link SailingServiceAsync#getBoatPositions(String, String, Map, Map, boolean, AsyncCallback)}, for each - * competitor from {@link #competitorsToShow} there are all fixes known by the server for that competitor starting - * at upTo-{@link #TAILLENGTHINMILLISECONDS} and ending at upTo (exclusive). - * - * @return a triple whose {@link Triple#getA() first} component contains the "from", and whose {@link Triple#getB() - * second} component contains the "to" times for the competitors whose trails / positions to show; the - * {@link Triple#getC() third} component tells whether the existing fixes can remain and be augmented by - * those requested or need to be replaced - */ - private Triple, Map, Map> computeFromAndTo( - Date upTo) { - Date tailStart = new Date(upTo.getTime() - TAILLENGTHINMILLISECONDS); - Map from = new HashMap(); - Map to = new HashMap(); - Map overlapWithKnownFixes = new HashMap(); - for (CompetitorDAO competitor : getCompetitorsToShow()) { - List fixesForCompetitor = fixes.get(competitor); - Date fromDate; - Date toDate; - Date timepointOfLastKnownFix = fixesForCompetitor == null ? null - : getTimepointOfLastNonExtrapolated(fixesForCompetitor); - Date timepointOfFirstKnownFix = fixesForCompetitor == null ? null - : getTimepointOfFirstNonExtrapolated(fixesForCompetitor); - boolean overlap = false; - if (fixesForCompetitor != null && timepointOfFirstKnownFix != null - && !tailStart.before(timepointOfFirstKnownFix) && timepointOfLastKnownFix != null - && !tailStart.after(timepointOfLastKnownFix)) { - // the beginning of what we need is contained in the interval we already have; skip what we already have - fromDate = timepointOfLastKnownFix; - overlap = true; - } else { - fromDate = tailStart; - } - if (fixesForCompetitor != null && timepointOfFirstKnownFix != null - && !upTo.before(timepointOfFirstKnownFix) && timepointOfLastKnownFix != null - && !upTo.after(timepointOfLastKnownFix)) { - // the end of what we need is contained in the interval we already have; skip what we already have - toDate = timepointOfFirstKnownFix; - overlap = true; - } else { - toDate = upTo; - } - // only request something for the competitor if we're missing information at all - if (fromDate.before(toDate) || fromDate.equals(toDate)) { - from.put(competitor, fromDate); - to.put(competitor, toDate); - overlapWithKnownFixes.put(competitor, overlap); - } - } - return new Triple, Map, Map>(from, to, - overlapWithKnownFixes); - } - - private Date getTimepointOfFirstNonExtrapolated(List fixesForCompetitor) { - for (GPSFixDAO fix : fixesForCompetitor) { - if (!fix.extrapolated) { - return fix.timepoint; - } - } - return null; - } - - private Date getTimepointOfLastNonExtrapolated(List fixesForCompetitor) { - if (!fixesForCompetitor.isEmpty()) { - for (ListIterator fixIter = fixesForCompetitor.listIterator(fixesForCompetitor.size() - 1); fixIter - .hasPrevious();) { - GPSFixDAO fix = fixIter.previous(); - if (!fix.extrapolated) { - return fix.timepoint; - } - } - } - return null; - } - - /** - * Adds the fixes received in result to {@link #fixes} and ensures they are still contiguous for each - * competitor. If overlapsWithKnownFixes indicates that the fixes received in result - * overlap with those already known, the fixes are merged into the list of already known fixes for the competitor. - * Otherwise, the fixes received in result replace those known so far for the respective competitor. - */ - private void updateFixes(Map> result, - Map overlapsWithKnownFixes) { - for (Map.Entry> e : result.entrySet()) { - if (e.getValue() != null && !e.getValue().isEmpty()) { - List fixesForCompetitor = fixes.get(e.getKey()); - if (fixesForCompetitor == null) { - fixesForCompetitor = new ArrayList(); - fixes.put(e.getKey(), fixesForCompetitor); - } - if (!overlapsWithKnownFixes.get(e.getKey())) { - fixesForCompetitor.clear(); - // to re-establish the invariants for tails, firstShownFix and lastShownFix, we now need to remove - // all - // points from the competitor's polyline and clear the entries in firstShownFix and lastShownFix - if (map != null && tails.containsKey(e.getKey())) { - map.removeOverlay(tails.remove(e.getKey())); - } - firstShownFix.remove(e.getKey()); - lastShownFix.remove(e.getKey()); - fixesForCompetitor.addAll(e.getValue()); - } else { - mergeFixes(e.getKey(), e.getValue()); - } - } - } - } - - /** - * While updating the {@link #fixes} for competitorDAO, the invariants for {@link #tails} and - * {@link #firstShownFix} and {@link #lastShownFix} are maintained: each time a fix is inserted, the - * {@link #firstShownFix}/{@link #lastShownFix} records for competitorDAO are incremented if they are - * greater or equal to the insertion index and we have a tail in {@link #tails} for competitorDAO. - * Additionally, if the fix is in between the fixes shown in the competitor's tail, the tail is adjusted by - * inserting the corresponding fix. - */ - private void mergeFixes(CompetitorDAO competitorDAO, List mergeThis) { - List intoThis = fixes.get(competitorDAO); - int indexOfFirstShownFix = firstShownFix.get(competitorDAO) == null ? -1 : firstShownFix.get(competitorDAO); - int indexOfLastShownFix = lastShownFix.get(competitorDAO) == null ? -1 : lastShownFix.get(competitorDAO); - Polyline tail = tails.get(competitorDAO); - int intoThisIndex = 0; - for (GPSFixDAO mergeThisFix : mergeThis) { - while (intoThisIndex < intoThis.size() - && intoThis.get(intoThisIndex).timepoint.before(mergeThisFix.timepoint)) { - intoThisIndex++; - } - if (intoThisIndex < intoThis.size() && intoThis.get(intoThisIndex).timepoint.equals(mergeThisFix.timepoint)) { - // exactly same time point; replace with fix from mergeThis - intoThis.set(intoThisIndex, mergeThisFix); - } else { - intoThis.add(intoThisIndex, mergeThisFix); - if (indexOfFirstShownFix >= intoThisIndex) { - indexOfFirstShownFix++; - } - if (indexOfLastShownFix >= intoThisIndex) { - indexOfLastShownFix++; - } - if (tail != null && intoThisIndex >= indexOfFirstShownFix && intoThisIndex <= indexOfLastShownFix) { - tail.insertVertex(intoThisIndex - indexOfFirstShownFix, - LatLng.newInstance(mergeThisFix.position.latDeg, mergeThisFix.position.lngDeg)); - } - } - intoThisIndex++; - } - } - private void showQuickRanks(List result) { quickRanksBox.clear(); quickRanksList.clear(); @@ -754,199 +378,6 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe } } - private void showMarksOnMap(List result) { - if (map != null) { - Set toRemove = new HashSet(buoyMarkers.keySet()); - for (MarkDAO markDAO : result) { - Marker buoyMarker = buoyMarkers.get(markDAO); - if (buoyMarker == null) { - buoyMarker = createBuoyMarker(markDAO); - buoyMarkers.put(markDAO, buoyMarker); - map.addOverlay(buoyMarker); - } else { - buoyMarker.setLatLng(LatLng.newInstance(markDAO.position.latDeg, markDAO.position.lngDeg)); - toRemove.remove(markDAO); - } - } - for (MarkDAO toRemoveMarkDAO : toRemove) { - Marker marker = buoyMarkers.remove(toRemoveMarkDAO); - map.removeOverlay(marker); - } - zoomMapFirstTimeToMarks(buoyMarkers.keySet()); - } - } - -/** - * Zooms the map to the given marks if the map was not zoomed yet. If the map zoom to the marks was successful one - * time, the initial zoom to these marks can not be done again except the race selection is changed via {@link RaceMapPanel#onRaceSelectionChange(List). - * - * @param marksToZoomAt - * the marks to zoom at - */ - private void zoomMapFirstTimeToMarks(Set marksToZoomAt) { - if (!mapZoomedOrPannedSinceLastRaceSelectionChange && !mapFirstZoomDone) { - LatLngBounds newBounds = null; - if (marksToZoomAt != null && !marksToZoomAt.isEmpty()) { - for (MarkDAO markDAO : marksToZoomAt) { - LatLng latLngZoomFirstTime = LatLng.newInstance(markDAO.position.latDeg, markDAO.position.lngDeg); - LatLngBounds bounds = LatLngBounds.newInstance(latLngZoomFirstTime, latLngZoomFirstTime); - if (newBounds == null) { - newBounds = bounds; - } else { - newBounds.extend(bounds.getNorthEast()); - newBounds.extend(bounds.getSouthWest()); - } - } - - } - if (newBounds != null) { - map.setZoomLevel(map.getBoundsZoomLevel(newBounds)); - map.setCenter(newBounds.getCenter()); - map.checkResizeAndCenter(); - mapFirstZoomDone = true; - /* - * Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that the map was just zoomed - * to the bounds of the buoys, it was not a zoom or pan triggered by the user. As a consequence the - * mapZoomedOrPannedSinceLastRaceSelection option has to reset again. - */ - mapZoomedOrPannedSinceLastRaceSelectionChange = false; - } - } - } - - /** - * @param from - * time point for first fix to show in tails - * @param to - * time point for last fix to show in tails - */ - private void showBoatsOnMap(Date from, Date to) { - if (map != null) { - LatLngBounds newMapBounds = null; - Set competitorDAOsOfUnusedTails = new HashSet(tails.keySet()); - Set competitorDAOsOfUnusedMarkers = new HashSet(boatMarkers.keySet()); - Collection competitorsToShow = getCompetitorsToShow(); - for (CompetitorDAO competitorDAO : competitorsToShow) { - if (fixes.containsKey(competitorDAO)) { - Polyline tail = tails.get(competitorDAO); - if (tail == null) { - tail = createTailAndUpdateIndices(competitorDAO, from, to); - map.addOverlay(tail); - } else { - updateTail(tail, competitorDAO, from, to); - competitorDAOsOfUnusedTails.remove(competitorDAO); - } - LatLngBounds bounds = tail.getBounds(); - if (newMapBounds == null) { - newMapBounds = bounds; - } else { - newMapBounds.extend(bounds.getNorthEast()); - newMapBounds.extend(bounds.getSouthWest()); - } - if (lastShownFix.containsKey(competitorDAO) && lastShownFix.get(competitorDAO) != -1) { - GPSFixDAO lastPos = getBoatFix(competitorDAO); - Marker boatMarker = boatMarkers.get(competitorDAO); - if (boatMarker == null) { - boatMarker = createBoatMarker(competitorDAO, false); - map.addOverlay(boatMarker); - boatMarkers.put(competitorDAO, boatMarker); - } else { - competitorDAOsOfUnusedMarkers.remove(competitorDAO); - // check if anchors match; re-use marker with setImage only if anchors match - Point newAnchor = getBoatImageRotator(lastPos, - competitorsSelectedInMap.contains(competitorDAO)).getAnchor(); - Point oldAnchor = boatMarker.getIcon().getIconAnchor(); - if (oldAnchor.getX() == newAnchor.getX() && oldAnchor.getY() == newAnchor.getY()) { - boatMarker.setLatLng(LatLng.newInstance(lastPos.position.latDeg, - lastPos.position.lngDeg)); - boatMarker.setImage(getBoatImageURL(lastPos, - competitorsSelectedInMap.contains(competitorDAO))); - } else { - // anchors don't match; replace marker - map.removeOverlay(boatMarker); - boatMarker = createBoatMarker(competitorDAO, - competitorsSelectedInMap.contains(competitorDAO)); - map.addOverlay(boatMarker); - boatMarkers.put(competitorDAO, boatMarker); - } - } - } - } - } - if (!mapZoomedOrPannedSinceLastRaceSelectionChange && newMapBounds != null) { - map.setZoomLevel(map.getBoundsZoomLevel(newMapBounds)); - map.setCenter(newMapBounds.getCenter()); - mapFirstZoomDone = true; - } - for (CompetitorDAO unusedMarkerCompetitorDAO : competitorDAOsOfUnusedMarkers) { - map.removeOverlay(boatMarkers.remove(unusedMarkerCompetitorDAO)); - } - for (CompetitorDAO unusedTailCompetitorDAO : competitorDAOsOfUnusedTails) { - map.removeOverlay(tails.remove(unusedTailCompetitorDAO)); - } - } - } - - private String getBoatImageURL(GPSFixDAO boatFix, boolean highlighted) { - return getBoatImageURL(getBoatImageRotator(boatFix, highlighted), boatFix); - } - - private String getBoatImageURL(ImageRotator boatImageRotator, GPSFixDAO boatFix) { - // the possible zoom level range is 0 to 21 (zoom level 0 would show the whole world) - int zoomLevel = map.getZoomLevel(); - double minScaleFactor = 0.1; - double maxScaleFactor = 1.0; - - double scaleDiffPerZoomLevel = (maxScaleFactor - minScaleFactor) / 21.0; - - double scaleFactor = minScaleFactor + scaleDiffPerZoomLevel * zoomLevel; - - return boatImageRotator.getRotatedImageURL(boatFix.speedWithBearing.bearingInDegrees, scaleFactor); - } - - private Icon getBoatImageIcon(GPSFixDAO boatFix, boolean highlighted) { - ImageRotator boatImageRotator = getBoatImageRotator(boatFix, highlighted); - Icon icon = Icon.newInstance(getBoatImageURL(boatImageRotator, boatFix)); - icon.setIconAnchor(boatImageRotator.getAnchor()); - return icon; - } - - private ImageRotator getBoatImageRotator(GPSFixDAO boatFix, boolean highlighted) { - if (boatFix.tack == Tack.PORT) { - if (LegType.DOWNWIND == boatFix.legType) { - if (highlighted) { - return boatIconHighlightedDownwindStarboardRotator; - } else { - return boatIconDownwindStarboardRotator; - } - } else { - if (highlighted) { - return boatIconHighlightedStarboardRotator; - } else { - return boatIconStarboardRotator; - } - } - } else { - if (LegType.DOWNWIND == boatFix.legType) { - if (highlighted) { - return boatIconHighlightedDownwindPortRotator; - } else { - return boatIconDownwindPortRotator; - } - } else { - if (highlighted) { - return boatIconHighlightedPortRotator; - } else { - return boatIconPortRotator; - } - } - } - } - - private GPSFixDAO getBoatFix(CompetitorDAO competitorDAO) { - return fixes.get(competitorDAO).get(lastShownFix.get(competitorDAO)); - } - private Collection getCompetitorsToShow() { if (showOnlySelected.getValue()) { return competitorsSelectedInMap; @@ -956,259 +387,6 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe } } - /** - * If the tail starts before from, removes leading vertices from tail that are before - * from. This is determined by using the {@link #firstShownFix} index which tells us where in - * {@link #fixes} we find the sequence of fixes currently represented in the tail. - *

- * - * If the tail starts after from, vertices for those {@link #fixes} for competitorDAO at - * or after time point from and before the time point of the first fix displayed so far in the tail and - * before to are prepended to the tail. - *

- * - * Now to the end of the tail: if the existing tail's end exceeds to, the vertices in excess are - * removed (aided by {@link #lastShownFix}). Otherwise, for the competitor's fixes starting at the tail's end up to - * to are appended to the tail. - *

- * - * When this method returns, {@link #firstShownFix} and {@link #lastShownFix} have been updated accordingly. - */ - private void updateTail(Polyline tail, CompetitorDAO competitorDAO, Date from, Date to) { - List fixesForCompetitor = fixes.get(competitorDAO); - int indexOfFirstShownFix = firstShownFix.get(competitorDAO) == null ? -1 : firstShownFix.get(competitorDAO); - while (indexOfFirstShownFix != -1 && tail.getVertexCount() > 0 - && fixesForCompetitor.get(indexOfFirstShownFix).timepoint.before(from)) { - tail.deleteVertex(0); - indexOfFirstShownFix++; - } - // now the polyline contains no more vertices representing fixes before "from"; - // go back in time starting at indexOfFirstShownFix while the fixes are still at or after "from" - // and insert corresponding vertices into the polyline - while (indexOfFirstShownFix > 0 && !fixesForCompetitor.get(indexOfFirstShownFix - 1).timepoint.before(from)) { - indexOfFirstShownFix--; - GPSFixDAO fix = fixesForCompetitor.get(indexOfFirstShownFix); - tail.insertVertex(0, LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg)); - } - // now adjust the polylines tail: remove excess vertices that are after "to" - int indexOfLastShownFix = lastShownFix.get(competitorDAO) == null ? -1 : lastShownFix.get(competitorDAO); - while (indexOfLastShownFix != -1 && tail.getVertexCount() > 0 - && fixesForCompetitor.get(indexOfLastShownFix).timepoint.after(to)) { - tail.deleteVertex(tail.getVertexCount() - 1); - indexOfLastShownFix--; - } - // now the polyline contains no more vertices representing fixes after "to"; - // go forward in time starting at indexOfLastShownFix while the fixes are still at or before "to" - // and insert corresponding vertices into the polyline - while (indexOfLastShownFix < fixesForCompetitor.size() - 1 - && !fixesForCompetitor.get(indexOfLastShownFix + 1).timepoint.after(to)) { - indexOfLastShownFix++; - GPSFixDAO fix = fixesForCompetitor.get(indexOfLastShownFix); - tail.insertVertex(tail.getVertexCount(), LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg)); - } - firstShownFix.put(competitorDAO, indexOfFirstShownFix); - lastShownFix.put(competitorDAO, indexOfLastShownFix); - } - - private Marker createBuoyMarker(final MarkDAO markDAO) { - MarkerOptions options = MarkerOptions.newInstance(); - if (buoyIcon != null) { - options.setIcon(buoyIcon); - } - options.setTitle(markDAO.name); - final Marker buoyMarker = new Marker(LatLng.newInstance(markDAO.position.latDeg, markDAO.position.lngDeg), - options); - buoyMarker.addMarkerClickHandler(new MarkerClickHandler() { - @Override - public void onClick(MarkerClickEvent event) { - LatLng latlng = buoyMarker.getLatLng(); - showMarkInfoWindow(markDAO, latlng); - } - }); - return buoyMarker; - } - - private Marker createBoatMarker(final CompetitorDAO competitorDAO, boolean highlighted) { - GPSFixDAO boatFix = getBoatFix(competitorDAO); - double latDeg = boatFix.position.latDeg; - double lngDeg = boatFix.position.lngDeg; - MarkerOptions options = MarkerOptions.newInstance(); - Icon icon = getBoatImageIcon(boatFix, highlighted); - options.setIcon(icon); - options.setTitle(competitorDAO.name); - final Marker boatMarker = new Marker(LatLng.newInstance(latDeg, lngDeg), options); - boatMarker.addMarkerClickHandler(new MarkerClickHandler() { - @Override - public void onClick(MarkerClickEvent event) { - LatLng latlng = boatMarker.getLatLng(); - showCompetitorInfoWindow(competitorDAO, latlng); - } - }); - boatMarker.addMarkerMouseOverHandler(new MarkerMouseOverHandler() { - @Override - public void onMouseOver(MarkerMouseOverEvent event) { - map.setTitle(competitorDAO.name); - // setSelectedInMap(competitorDAO, true); - // quickRanksBox.setItemSelected(quickRanksList.indexOf(competitorDAO), true); - } - }); - boatMarker.addMarkerMouseOutHandler(new MarkerMouseOutHandler() { - @Override - public void onMouseOut(MarkerMouseOutEvent event) { - map.setTitle(""); - } - }); - return boatMarker; - } - - private void showMarkInfoWindow(MarkDAO markDAO, LatLng latlng) { - map.getInfoWindow().open(latlng, new InfoWindowContent(getInfoWindowContent(markDAO))); - } - - private void showCompetitorInfoWindow(final CompetitorDAO competitorDAO, LatLng where) { - GPSFixDAO latestFixForCompetitor = getBoatFix(competitorDAO); - map.getInfoWindow().open(where, - new InfoWindowContent(getInfoWindowContent(competitorDAO, latestFixForCompetitor))); - } - - private Widget getInfoWindowContent(MarkDAO markDAO) { - VerticalPanel result = new VerticalPanel(); - result.add(new Label("Mark " + markDAO.name)); - result.add(new Label("" + markDAO.position)); - return result; - } - - private Widget getInfoWindowContent(CompetitorDAO competitorDAO, GPSFixDAO lastFix) { - final VerticalPanel result = new VerticalPanel(); - result.add(new Label("Competitor " + competitorDAO.name)); - result.add(new Label("" + lastFix.position)); - result.add(new Label(lastFix.speedWithBearing.speedInKnots + "kts " + lastFix.speedWithBearing.bearingInDegrees - + "deg")); - result.add(new Label("Tack: " + lastFix.tack.name())); - List> selection = newRaceListBox.getSelectedEventAndRace(); - if (!selection.isEmpty()) { - EventDAO event = selection.get(selection.size() - 1).getA(); - RaceDAO race = selection.get(selection.size() - 1).getC(); - if (event != null && race != null) { - Map from = new HashMap(); - from.put(competitorDAO, fixes.get(competitorDAO).get(firstShownFix.get(competitorDAO)).timepoint); - Map to = new HashMap(); - to.put(competitorDAO, getBoatFix(competitorDAO).timepoint); - /* currently not showing Douglas-Peucker points; TODO use checkboxes to select what to show (Bug #6) */ - sailingService.getDouglasPoints(new EventNameAndRaceName(event.name, race.name), from, to, 3, - new AsyncCallback>>() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error obtaining douglas positions: " + caught.getMessage()); - } - - @Override - public void onSuccess(Map> result) { - RaceMapPanel.this.lastDouglasPeuckerResult = result; - if (douglasMarkers != null) { - removeAllMarkDouglasPeuckerpoints(); - } - if (!timer.isPlaying()) { - if (checkBoxDouglasPeuckerPoints.getValue()) { - showMarkDouglasPeuckerPoints(result); - } - } - } - }); - sailingService.getManeuvers(new EventNameAndRaceName(event.name, race.name), from, to, - new AsyncCallback>>() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error obtaining maneuvers: " + caught.getMessage()); - } - - @Override - public void onSuccess(Map> result) { - RaceMapPanel.this.lastManeuverResult = result; - if (maneuverMarkers != null) { - removeAllManeuverMarkers(); - } - if (!timer.isPlaying()) { - showManeuvers(result); - } - } - }); - - } - } - return result; - } - - private String getColorString(CompetitorDAO competitorDAO) { - // TODO green no more than 70, red no less than 120 - return "#" + (Integer.toHexString(competitorDAO.hashCode()) + "000000").substring(0, 4).toUpperCase() + "00"; - } - - /** - * Creates a polyline for the competitor represented by competitorDAO, taking the fixes from - * {@link #fixes fixes.get(competitorDAO)} and using the fixes starting at time point from (inclusive) - * up to the last fix with time point before to. The polyline is returned. Updates are applied to - * {@link #lastShownFix}, {@link #firstShownFix} and {@link #tails}. - */ - private Polyline createTailAndUpdateIndices(final CompetitorDAO competitorDAO, Date from, Date to) { - List points = new ArrayList(); - List fixesForCompetitor = fixes.get(competitorDAO); - int indexOfFirst = -1; - int indexOfLast = -1; - int i = 0; - for (Iterator fixIter = fixesForCompetitor.iterator(); fixIter.hasNext() && indexOfLast == -1;) { - GPSFixDAO fix = fixIter.next(); - if (!fix.timepoint.before(to)) { - indexOfLast = i; - } else { - LatLng point = null; - if (indexOfFirst == -1) { - if (!fix.timepoint.before(from)) { - indexOfFirst = i; - point = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg); - } - } else { - point = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg); - } - if (point != null) { - points.add(point); - } - } - i++; - } - if (indexOfLast == -1) { - indexOfLast = i - 1; - } - if (indexOfFirst != -1 && indexOfLast != -1) { - firstShownFix.put(competitorDAO, indexOfFirst); - lastShownFix.put(competitorDAO, indexOfLast); - } - PolylineOptions options = PolylineOptions.newInstance( - /* clickable */true, /* geodesic */true); - Polyline result = new Polyline(points.toArray(new LatLng[0]), getColorString(competitorDAO), /* width */3, - /* opacity */0.5, options); - result.addPolylineClickHandler(new PolylineClickHandler() { - @Override - public void onClick(PolylineClickEvent event) { - showCompetitorInfoWindow(competitorDAO, lastMousePosition); - } - }); - result.addPolylineMouseOverHandler(new PolylineMouseOverHandler() { - @Override - public void onMouseOver(PolylineMouseOverEvent event) { - map.setTitle(competitorDAO.name); - } - }); - result.addPolylineMouseOutHandler(new PolylineMouseOutHandler() { - @Override - public void onMouseOut(PolylineMouseOutEvent event) { - map.setTitle(""); - } - }); - tails.put(competitorDAO, result); - return result; - } - /* * (non-Javadoc) * @@ -1222,114 +400,27 @@ public class RaceMapPanel extends FormPanel implements EventDisplayer, TimeListe ((RequiresResize) child).onResize(); } // and ensure the map (indirect child) is also informed about resize - if (this.map != null) { - this.map.onResize(); + if (raceMap.map != null) { + raceMap.map.onResize(); } } - private void removeAllMarkDouglasPeuckerpoints() { - if (douglasMarkers != null) { - for (Marker marker : douglasMarkers) { - map.removeOverlay(marker); - } - } - douglasMarkers = null; + private void setMapDisplayOptions() + { + raceMap.showManeuverTack = getCheckboxValueManeuver("TACK"); + raceMap.showManeuverJibe = getCheckboxValueManeuver("JIBE"); + raceMap.showManeuverHeadUp = getCheckboxValueManeuver("HEAD_UP"); + raceMap.showManeuverBearAway = getCheckboxValueManeuver("BEAR_AWAY"); + raceMap.showManeuverPenaltyCircle = getCheckboxValueManeuver("PENALTY_CIRCLE"); + raceMap.showManeuverMarkPassing = getCheckboxValueManeuver("MARK_PASSING"); + raceMap.showManeuverOther = getCheckboxValueManeuver("OTHER"); + + raceMap.showDouglasPeuckerPoints = checkBoxDouglasPeuckerPoints.getValue(); } - - private void removeAllManeuverMarkers() { - if (maneuverMarkers != null) { - for (Marker marker : maneuverMarkers) { - map.removeOverlay(marker); - } - maneuverMarkers = null; - } - } - - private void showMarkDouglasPeuckerPoints(Map> gpsFixPointMapForCompetitors) { - douglasMarkers = new HashSet(); - if (map != null && gpsFixPointMapForCompetitors != null) { - Set keySet = gpsFixPointMapForCompetitors.keySet(); - Iterator iter = keySet.iterator(); - while (iter.hasNext()) { - CompetitorDAO competitorDAO = iter.next(); - List gpsFix = gpsFixPointMapForCompetitors.get(competitorDAO); - for (GPSFixDAO fix : gpsFix) { - LatLng latLng = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg); - MarkerOptions options = MarkerOptions.newInstance(); - options.setTitle(fix.timepoint+": "+fix.position+", "+fix.speedWithBearing.toString()); - Marker marker = new Marker(latLng, options); - douglasMarkers.add(marker); - map.addOverlay(marker); - } - } - } - } - + private void showManeuvers(Map> maneuvers) { - maneuverMarkers = new HashSet(); - if (map != null && maneuvers != null) { - Set keySet = maneuvers.keySet(); - Iterator iter = keySet.iterator(); - while (iter.hasNext()) { - CompetitorDAO competitorDAO = iter.next(); - List maneuversForCompetitor = maneuvers.get(competitorDAO); - for (ManeuverDAO maneuver : maneuversForCompetitor) { - boolean showThisManeuver = true; - LatLng latLng = LatLng.newInstance(maneuver.position.latDeg, maneuver.position.lngDeg); - MarkerOptions options = MarkerOptions.newInstance(); - options.setTitle("" + maneuver.timepoint + ": " + maneuver.type + " " - + maneuver.directionChangeInDegrees + "deg from " + maneuver.speedWithBearingBefore - + " to " + maneuver.speedWithBearingAfter); - if (maneuver.type.equals("TACK") && getCheckboxValueManeuver("TACK")) { - if (maneuver.newTack.equals("PORT")) { - options.setIcon(tackToPortIcon); - } else { - options.setIcon(tackToStarboardIcon); - } - } else if (maneuver.type.equals("JIBE") && getCheckboxValueManeuver("JIBE")) { - if (maneuver.newTack.equals("PORT")) { - options.setIcon(jibeToPortIcon); - } else { - options.setIcon(jibeToStarboardIcon); - } - } else if (maneuver.type.equals("HEAD_UP") && getCheckboxValueManeuver("HEAD_UP")) { - if (maneuver.newTack.equals("PORT")) { - options.setIcon(headUpOnPortIcon); - } else { - options.setIcon(headUpOnStarboardIcon); - } - } else if (maneuver.type.equals("BEAR_AWAY") && getCheckboxValueManeuver("BEAR_AWAY")) { - if (maneuver.newTack.equals("PORT")) { - options.setIcon(bearAwayOnPortIcon); - } else { - options.setIcon(bearAwayOnStarboardIcon); - } - } else if (maneuver.type.equals("PENALTY_CIRCLE") && getCheckboxValueManeuver("PENALTY_CIRCLE")) { - if (maneuver.newTack.equals("PORT")) { - options.setIcon(penaltyCircleToPortIcon); - } else { - options.setIcon(penaltyCircleToStarboardIcon); - } - } else if (maneuver.type.equals("MARK_PASSING") && getCheckboxValueManeuver("MARK_PASSING")) { - if (maneuver.newTack.equals("PORT")) { - options.setIcon(markPassingToPortIcon); - } else { - options.setIcon(markPassingToStarboardIcon); - } - } else { - if (maneuver.type.equals("UNKNOWN") && getCheckboxValueManeuver("OTHER")) { - options.setIcon(unknownManeuverIcon); - } else { - showThisManeuver = false; - } - } - if (showThisManeuver) { - Marker marker = new Marker(latLng, options); - maneuverMarkers.add(marker); - map.addOverlay(marker); - } - } - } - } + setMapDisplayOptions(); + + raceMap.showManeuvers(maneuvers); } }