/* This file is part of RouteConverter. RouteConverter is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. RouteConverter is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RouteConverter; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Copyright (C) 2007 Christian Pesch. All Rights Reserved. */ package slash.navigation.mapview.mapsforge.renderer; import org.mapsforge.core.graphics.GraphicFactory; import org.mapsforge.core.graphics.Paint; import org.mapsforge.core.model.LatLong; import slash.navigation.common.LongitudeAndLatitude; import slash.navigation.common.NavigationPosition; import slash.navigation.converter.gui.models.ColorModel; import slash.navigation.common.DistanceAndTime; import slash.navigation.mapview.mapsforge.MapViewCallbackOffline; import slash.navigation.mapview.mapsforge.MapsforgeMapView; import slash.navigation.mapview.mapsforge.lines.Line; import slash.navigation.mapview.mapsforge.lines.Polyline; import slash.navigation.mapview.mapsforge.models.IntermediateRoute; import slash.navigation.mapview.mapsforge.updater.PairWithLayer; import slash.navigation.routing.DownloadFuture; import slash.navigation.routing.RoutingResult; import slash.navigation.routing.RoutingService; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import java.util.prefs.Preferences; import static java.lang.System.currentTimeMillis; import static java.lang.Thread.sleep; import static slash.common.helpers.ThreadHelper.safeJoin; import static slash.common.io.Transfer.isEmpty; import static slash.navigation.maps.helpers.MapTransfer.asLatLong; import static slash.navigation.mapview.MapViewConstants.ROUTE_LINE_WIDTH_PREFERENCE; import static slash.navigation.mapview.mapsforge.helpers.ColorHelper.asRGBA; /** * Renders a {@link List} of {@link PairWithLayer} for the {@link MapsforgeMapView}. * * @author Christian Pesch */ public class RouteRenderer { private static final Logger log = Logger.getLogger(RouteRenderer.class.getName()); private static final Preferences preferences = Preferences.userNodeForPackage(MapsforgeMapView.class); private Paint ROUTE_NOT_VALID_PAINT, ROUTE_DOWNLOADING_PAINT; private Thread renderThread = null; private final Object notificationMutex = new Object(); private boolean drawingRoute = false; private MapsforgeMapView mapView; private MapViewCallbackOffline mapViewCallback; private ColorModel routeColorModel; private GraphicFactory graphicFactory; public RouteRenderer(MapsforgeMapView mapView, MapViewCallbackOffline mapViewCallback, ColorModel routeColorModel, GraphicFactory graphicFactory) { this.mapView = mapView; this.mapViewCallback = mapViewCallback; this.routeColorModel = routeColorModel; this.graphicFactory = graphicFactory; initialize(); } private void initialize() { ROUTE_NOT_VALID_PAINT = graphicFactory.createPaint(); ROUTE_NOT_VALID_PAINT.setColor(0xFFFF0000); ROUTE_NOT_VALID_PAINT.setStrokeWidth(5); ROUTE_DOWNLOADING_PAINT = graphicFactory.createPaint(); ROUTE_DOWNLOADING_PAINT.setColor(0x993379FF); ROUTE_DOWNLOADING_PAINT.setStrokeWidth(5); ROUTE_DOWNLOADING_PAINT.setDashPathEffect(new float[]{3, 12}); } public void dispose() { long start = currentTimeMillis(); synchronized (notificationMutex) { this.drawingRoute = false; } if (renderThread != null) { try { safeJoin(renderThread); } catch (InterruptedException e) { // intentionally left empty } long end = currentTimeMillis(); log.info("RouteRenderer stopped after " + (end - start) + " ms"); } } public void renderRoute(final List<PairWithLayer> pairWithLayers, final Runnable invokeAfterRenderingRunnable) { dispose(); renderThread = new Thread(new Runnable() { public void run() { synchronized (notificationMutex) { RouteRenderer.this.drawingRoute = true; } try { internalRenderRoute(pairWithLayers, invokeAfterRenderingRunnable); } catch (Throwable t) { mapViewCallback.handleRoutingException(t); } } }, "RouteRenderer"); renderThread.start(); } private void checkForInterruption() { synchronized (notificationMutex) { if(!drawingRoute) renderThread.interrupt(); } } private void internalRenderRoute(List<PairWithLayer> pairWithLayers, Runnable invokeAfterRenderingRunnable) { drawBeeline(pairWithLayers); checkForInterruption(); RoutingService service = mapViewCallback.getRoutingService(); waitForInitialization(service); waitForDownload(service, pairWithLayers); checkForInterruption(); drawRoute(pairWithLayers); invokeAfterRenderingRunnable.run(); checkForInterruption(); } private void waitForInitialization(RoutingService service) { if (!service.isInitialized()) { while (!service.isInitialized()) { try { sleep(100); } catch (InterruptedException e) { // intentionally left empty } } } } private LongitudeAndLatitude asLongitudeAndLatitude(NavigationPosition position) { return new LongitudeAndLatitude(position.getLongitude(), position.getLatitude()); } private List<LongitudeAndLatitude> asLongitudeAndLatitude(List<PairWithLayer> pairWithLayers) { List<LongitudeAndLatitude> result = new ArrayList<>(); for (PairWithLayer pairWithLayer : pairWithLayers) { if(!pairWithLayer.hasCoordinates()) continue; result.add(asLongitudeAndLatitude(pairWithLayer.getFirst())); result.add(asLongitudeAndLatitude(pairWithLayer.getSecond())); } return result; } private void waitForDownload(RoutingService service, List<PairWithLayer> pairWithLayers) { if (service.isDownload()) { DownloadFuture future = service.downloadRoutingDataFor(asLongitudeAndLatitude(pairWithLayers)); if (future.isRequiresDownload()) { mapViewCallback.showDownloadNotification(); future.download(); } if (future.isRequiresProcessing()) { mapViewCallback.showProcessNotification(); future.process(); } } } private void drawBeeline(List<PairWithLayer> pairsWithLayer) { for (PairWithLayer pairWithLayer : pairsWithLayer) { if (!pairWithLayer.hasCoordinates()) continue; Line line = new Line(asLatLong(pairWithLayer.getFirst()), asLatLong(pairWithLayer.getSecond()), ROUTE_DOWNLOADING_PAINT, mapView.getTileSize()); pairWithLayer.setLayer(line); mapView.addLayer(line); Double distance = pairWithLayer.getFirst().calculateDistance(pairWithLayer.getSecond()); Long time = pairWithLayer.getFirst().calculateTime(pairWithLayer.getSecond()); pairWithLayer.setDistanceAndTime(new DistanceAndTime(distance, !isEmpty(time) ? time / 1000 : null)); } } private void drawRoute(List<PairWithLayer> pairWithLayers) { Paint paint = graphicFactory.createPaint(); paint.setColor(asRGBA(routeColorModel)); paint.setStrokeWidth(preferences.getInt(ROUTE_LINE_WIDTH_PREFERENCE, 5)); RoutingService routingService = mapViewCallback.getRoutingService(); for (PairWithLayer pairWithLayer : pairWithLayers) { if (!pairWithLayer.hasCoordinates()) continue; IntermediateRoute intermediateRoute = calculateRoute(routingService, pairWithLayer); Polyline polyline = new Polyline(intermediateRoute.getLatLongs(), intermediateRoute.isValid() ? paint : ROUTE_NOT_VALID_PAINT, mapView.getTileSize()); // remove beeline layer then add polyline layer from routing mapView.removeLayer(pairWithLayer); pairWithLayer.setLayer(polyline); mapView.addLayer(polyline); } } private IntermediateRoute calculateRoute(RoutingService routingService, PairWithLayer pairWithLayer) { List<LatLong> latLongs = new ArrayList<>(); latLongs.add(asLatLong(pairWithLayer.getFirst())); RoutingResult result = routingService.getRouteBetween(pairWithLayer.getFirst(), pairWithLayer.getSecond(), mapViewCallback.getTravelMode()); if (result.isValid()) latLongs.addAll(asLatLong(result.getPositions())); pairWithLayer.setDistanceAndTime(result.getDistanceAndTime()); latLongs.add(asLatLong(pairWithLayer.getSecond())); return new IntermediateRoute(latLongs, result.isValid()); } }