diff --git a/OsmAnd/src/net/osmand/plus/views/GPXLayer.java b/OsmAnd/src/net/osmand/plus/views/GPXLayer.java index 7cc2e97f63..5a4fc50cc0 100644 --- a/OsmAnd/src/net/osmand/plus/views/GPXLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/GPXLayer.java @@ -1,5 +1,7 @@ package net.osmand.plus.views; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; @@ -108,6 +110,9 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM private Paint paintTextIcon; + private Bitmap arrowBitmap; + private GeometryWayContext wayContext; + private OsmandRenderer osmandRenderer; private ContextMenuLayer contextMenuLayer; @@ -181,6 +186,9 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM visitedColor = ContextCompat.getColor(view.getApplication(), R.color.color_ok); defPointColor = ContextCompat.getColor(view.getApplication(), R.color.gpx_color_point); grayColor = ContextCompat.getColor(view.getApplication(), R.color.color_favorite_gray); + + wayContext = new GeometryWayContext(view.getContext(), view.getDensity()); + arrowBitmap = BitmapFactory.decodeResource(view.getApplication().getResources(), R.drawable.map_route_direction_arrow, null); } @Override @@ -208,6 +216,7 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM if (trackChartPoints != null) { drawXAxisPoints(canvas, tileBox); } + drawDirectionArrows(canvas, tileBox, selectedGPXFiles); drawSelectedFilesSplits(canvas, tileBox, selectedGPXFiles, settings); drawSelectedFilesPoints(canvas, tileBox, selectedGPXFiles); drawSelectedFilesStartEndPoints(canvas, tileBox, selectedGPXFiles); @@ -393,6 +402,127 @@ public class GPXLayer extends OsmandMapLayer implements IContextMenuProvider, IM } } + private void drawDirectionArrows(Canvas canvas, RotatedTileBox tileBox, List selectedGPXFiles) { + if (!tileBox.isZoomAnimated()) { + for (SelectedGpxFile selectedGpxFile : selectedGPXFiles) { + boolean showArrows = selectedGpxFile.getGpxFile().isShowArrows(); + if (showArrows) { + QuadRect correctedQuadRect = getCorrectedQuadRect(tileBox.getLatLonBounds()); + int color = selectedGpxFile.getGpxFile().getColor(cachedColor); + if (selectedGpxFile.isShowCurrentTrack()) { + color = currentTrackColor; + } + int contrastColor = UiUtilities.getContrastColor(view.getApplication(), color, false); + GeometryWayStyle arrowsWayStyle = new GeometryArrowsWayStyle(wayContext, contrastColor); + for (TrkSegment segment : selectedGpxFile.getPointsToDisplay()) { + List tx = new ArrayList<>(); + List ty = new ArrayList<>(); + List distances = new ArrayList<>(); + List angles = new ArrayList<>(); + List styles = new ArrayList<>(); + boolean previousVisible = false; + + List points = segment.points; + if (points.size() > 1) { + for (int i = 0; i < points.size(); i++) { + WptPt pt = points.get(i); + if (correctedQuadRect.left <= pt.getLongitude() + && pt.getLongitude() <= correctedQuadRect.right + && correctedQuadRect.bottom <= pt.getLatitude() + && pt.getLatitude() <= correctedQuadRect.top) { + addLocation(tileBox, pt.getLatitude(), pt.getLongitude(), null, tx, ty, angles, distances, 0, styles); + previousVisible = true; + } else if (previousVisible) { + addLocation(tileBox, pt.getLatitude(), pt.getLongitude(), null, tx, ty, angles, distances, 0, styles); + previousVisible = false; + } + } + drawArrowsOverPath(tx, ty, angles, distances, canvas, tileBox, arrowsWayStyle); + } + } + } + } + } + } + + private void drawArrowsOverPath(List tx, List ty, List angles, List distances, + Canvas canvas, RotatedTileBox tb, GeometryWayStyle wayStyle) { + int pixHeight = tb.getPixHeight(); + int pixWidth = tb.getPixWidth(); + int left = -pixWidth / 4; + int right = pixWidth + pixWidth / 4; + int top = -pixHeight / 4; + int bottom = pixHeight + pixHeight / 4; + + double zoomCoef = tb.getZoomAnimation() > 0 ? (Math.pow(2, tb.getZoomAnimation() + tb.getZoomFloatPart())) : 1f; + double pxStep = arrowBitmap.getHeight() * 4f * zoomCoef; + double dist = 0; + + List arrows = new ArrayList<>(); + for (int i = tx.size() - 2; i >= 0; i--) { + float px = tx.get(i); + float py = ty.get(i); + float x = tx.get(i + 1); + float y = ty.get(i + 1); + double angle = angles.get(i + 1); + double distSegment = distances.get(i + 1); + if (distSegment == 0) { + continue; + } + if (dist >= pxStep) { + dist = 0; + } + double percent = 1 - (pxStep - dist) / distSegment; + dist += distSegment; + while (dist >= pxStep) { + double pdx = (x - px) * percent; + double pdy = (y - py) * percent; + float iconx = (float) (px + pdx); + float icony = (float) (py + pdy); + if (isIn(iconx, icony, left, top, right, bottom)) { + arrows.add(new PathPoint(iconx, icony, angle, wayStyle)); + } + dist -= pxStep; + percent -= pxStep / distSegment; + } + } + for (int i = arrows.size() - 1; i >= 0; i--) { + PathPoint a = arrows.get(i); + a.draw(canvas, wayContext); + } + } + + private static class GeometryArrowsWayStyle extends GeometryWayStyle { + + protected Integer pointColor; + + GeometryArrowsWayStyle(GeometryWayContext context, int pointColor) { + super(context); + this.pointColor = pointColor; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!super.equals(other)) { + return false; + } + return other instanceof GeometryArrowsWayStyle; + } + + @Override + public Bitmap getPointBitmap() { + return getContext().getArrowBitmap(); + } + + @Override + public Integer getPointColor() { + return pointColor; + } + } + private void drawSelectedFilesStartEndPoints(Canvas canvas, RotatedTileBox tileBox, List selectedGPXFiles) { if (tileBox.getZoom() >= START_ZOOM) { for (SelectedGpxFile selectedGpxFile : selectedGPXFiles) { diff --git a/OsmAnd/src/net/osmand/plus/views/OsmandMapLayer.java b/OsmAnd/src/net/osmand/plus/views/OsmandMapLayer.java index 700cb9a7a2..462927e0cd 100644 --- a/OsmAnd/src/net/osmand/plus/views/OsmandMapLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/OsmandMapLayer.java @@ -6,6 +6,7 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint.Cap; import android.graphics.Paint.Join; @@ -470,6 +471,80 @@ public abstract class OsmandMapLayer { } } + public static class PathPoint { + float x; + float y; + double angle; + GeometryWayStyle style; + + private Matrix matrix = new Matrix(); + + PathPoint(float x, float y, double angle, GeometryWayStyle style) { + this.x = x; + this.y = y; + this.angle = angle; + this.style = style; + } + + protected Matrix getMatrix() { + return matrix; + } + + void draw(Canvas canvas, GeometryWayContext context) { + if (style != null && style.getPointBitmap() != null) { + Bitmap bitmap = style.getPointBitmap(); + Integer pointColor = style.getPointColor(); + float paintH2 = bitmap.getHeight() / 2f; + float paintW2 = bitmap.getWidth() / 2f; + + matrix.reset(); + matrix.postRotate((float) angle, paintW2, paintH2); + matrix.postTranslate(x - paintW2, y - paintH2); + if (pointColor != null) { + Paint paint = context.getPaintIconCustom(); + paint.setColorFilter(new PorterDuffColorFilter(pointColor, Mode.SRC_IN)); + canvas.drawBitmap(bitmap, matrix, paint); + } else { + if (style.hasPaintedPointBitmap()) { + Paint paint = context.getPaintIconCustom(); + paint.setColorFilter(null); + canvas.drawBitmap(bitmap, matrix, paint); + } else { + canvas.drawBitmap(bitmap, matrix, context.getPaintIcon()); + } + } + } + } + } + + protected void addLocation(RotatedTileBox tb, double latitude, double longitude, GeometryWayStyle style, + List tx, List ty, List angles, List distances, + double dist, List styles) { + float x = tb.getPixXFromLatLon(latitude, longitude); + float y = tb.getPixYFromLatLon(latitude, longitude); + float px = x; + float py = y; + int previous = tx.size() - 1; + if (previous >= 0) { + px = tx.get(previous); + py = ty.get(previous); + } + double angle = 0; + if (px != x || py != y) { + double angleRad = Math.atan2(y - py, x - px); + angle = (angleRad * 180 / Math.PI) + 90f; + } + double distSegment = Math.sqrt((y - py) * (y - py) + (x - px) * (x - px)); + if (dist != 0) { + distSegment = dist; + } + tx.add(x); + ty.add(y); + angles.add(angle); + distances.add(distSegment); + styles.add(style); + } + public int calculatePath(RotatedTileBox tb, List xs, List ys, List styles, List> paths) { boolean segmentStarted = false; float prevX = xs.get(0); @@ -561,6 +636,20 @@ public abstract class OsmandMapLayer { return false; } + public QuadRect getCorrectedQuadRect(QuadRect latlonRect) { + double topLatitude = latlonRect.top; + double leftLongitude = latlonRect.left; + double bottomLatitude = latlonRect.bottom; + double rightLongitude = latlonRect.right; + // double lat = 0; + // double lon = 0; + // this is buggy lat/lon should be 0 but in that case + // it needs to be fixed in case there is no route points in the view bbox + double lat = topLatitude - bottomLatitude + 0.1; + double lon = rightLongitude - leftLongitude + 0.1; + return new QuadRect(leftLongitude - lon, topLatitude + lat, rightLongitude + lon, bottomLatitude - lat); + } + public QuadRect calculateRect(float x, float y, float width, float height) { QuadRect rf; double left = x - width / 2.0d; diff --git a/OsmAnd/src/net/osmand/plus/views/RouteLayer.java b/OsmAnd/src/net/osmand/plus/views/RouteLayer.java index 8a31024608..299d9d01a1 100644 --- a/OsmAnd/src/net/osmand/plus/views/RouteLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/RouteLayer.java @@ -186,17 +186,8 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont } final QuadRect latlonRect = cp.getLatLonBounds(); - double topLatitude = latlonRect.top; - double leftLongitude = latlonRect.left; - double bottomLatitude = latlonRect.bottom; - double rightLongitude = latlonRect.right; - // double lat = 0; - // double lon = 0; - // this is buggy lat/lon should be 0 but in that case - // it needs to be fixed in case there is no route points in the view bbox - double lat = topLatitude - bottomLatitude + 0.1; - double lon = rightLongitude - leftLongitude + 0.1; - drawLocations(tileBox, canvas, topLatitude + lat, leftLongitude - lon, bottomLatitude - lat, rightLongitude + lon); + final QuadRect correctedQuadRect = getCorrectedQuadRect(latlonRect); + drawLocations(tileBox, canvas, correctedQuadRect.top, correctedQuadRect.left, correctedQuadRect.bottom, correctedQuadRect.right); if (trackChartPoints != null) { canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); @@ -372,52 +363,6 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont } } - private static class PathPoint { - float x; - float y; - double angle; - GeometryWayStyle style; - - private Matrix matrix = new Matrix(); - - PathPoint(float x, float y, double angle, GeometryWayStyle style) { - this.x = x; - this.y = y; - this.angle = angle; - this.style = style; - } - - protected Matrix getMatrix() { - return matrix; - } - - void draw(Canvas canvas, GeometryWayContext context) { - if (style != null && style.getPointBitmap() != null) { - Bitmap bitmap = style.getPointBitmap(); - Integer pointColor = style.getPointColor(); - float paintH2 = bitmap.getHeight() / 2f; - float paintW2 = bitmap.getWidth() / 2f; - - matrix.reset(); - matrix.postRotate((float) angle, paintW2, paintH2); - matrix.postTranslate(x - paintW2, y - paintH2); - if (pointColor != null) { - Paint paint = context.getPaintIconCustom(); - paint.setColorFilter(new PorterDuffColorFilter(pointColor, Mode.SRC_IN)); - canvas.drawBitmap(bitmap, matrix, paint); - } else { - if (style.hasPaintedPointBitmap()) { - Paint paint = context.getPaintIconCustom(); - paint.setColorFilter(null); - canvas.drawBitmap(bitmap, matrix, paint); - } else { - canvas.drawBitmap(bitmap, matrix, context.getPaintIcon()); - } - } - } - } - } - private static class PathAnchor extends PathPoint { PathAnchor(float x, float y, GeometryAnchorWayStyle style) { super(x, y, 0, style); @@ -1003,14 +948,14 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont } else if (lastProjection != null) { lt = lastProjection; } - if(lt != null) { - addLocation(tb, lt, style, tx, ty, angles, distances, 0, styles); // first point + if (lt != null) { + addLocation(tb, lt.getLatitude(), lt.getLongitude(), style, tx, ty, angles, distances, 0, styles); // first point } } - addLocation(tb, ls, style, tx, ty, angles, distances, dist, styles); + addLocation(tb, ls.getLatitude(), ls.getLongitude(), style, tx, ty, angles, distances, dist, styles); previousVisible = true; } else if (previousVisible) { - addLocation(tb, ls, style, tx, ty, angles, distances, previous == -1 ? 0 : odistances.get(i), styles); + addLocation(tb, ls.getLatitude(), ls.getLongitude(), style, tx, ty, angles, distances, previous == -1 ? 0 : odistances.get(i), styles); double distToFinish = 0; for(int ki = i + 1; ki < odistances.size(); ki++) { distToFinish += odistances.get(ki); @@ -1027,7 +972,7 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont private boolean addPoint(RotatedTileBox tb, double topLatitude, double leftLongitude, double bottomLatitude, double rightLongitude, GeometryWayStyle style, boolean previousVisible, Location lastPoint) { if (leftLongitude <= lastPoint .getLongitude() && lastPoint .getLongitude() <= rightLongitude && bottomLatitude <= lastPoint .getLatitude() && lastPoint .getLatitude() <= topLatitude) { - addLocation(tb, lastPoint, style, tx, ty, angles, distances, 0, styles); + addLocation(tb, lastPoint.getLatitude(), lastPoint.getLongitude(), style, tx, ty, angles, distances, 0, styles); previousVisible = true; } return previousVisible; @@ -1040,33 +985,6 @@ public class RouteLayer extends OsmandMapLayer implements ContextMenuLayer.ICont angles.clear(); styles.clear(); } - - private void addLocation(RotatedTileBox tb, Location ls, GeometryWayStyle style, List tx, List ty, - List angles, List distances, double dist, List styles) { - float x = tb.getPixXFromLatLon(ls.getLatitude(), ls.getLongitude()); - float y = tb.getPixYFromLatLon(ls.getLatitude(), ls.getLongitude()); - float px = x; - float py = y; - int previous = tx.size() - 1; - if (previous >= 0 && previous < tx.size()) { - px = tx.get(previous); - py = ty.get(previous); - } - double angle = 0; - if (px != x || py != y) { - double angleRad = Math.atan2(y - py, x - px); - angle = (angleRad * 180 / Math.PI) + 90f; - } - double distSegment = Math.sqrt((y - py) * (y - py) + (x - px) * (x - px)); - if(dist != 0) { - distSegment = dist; - } - tx.add(x); - ty.add(y); - angles.add(angle); - distances.add(distSegment); - styles.add(style); - } } private RouteSimplificationGeometry routeGeometry = new RouteSimplificationGeometry();