diff --git a/OsmAnd/src/net/osmand/AndroidUtils.java b/OsmAnd/src/net/osmand/AndroidUtils.java index 42b4a60919..4f0729d355 100644 --- a/OsmAnd/src/net/osmand/AndroidUtils.java +++ b/OsmAnd/src/net/osmand/AndroidUtils.java @@ -151,6 +151,10 @@ public class AndroidUtils { return resizedBitmap; } + public static Bitmap createScaledBitmap(Drawable drawable, int width, int height) { + return scaleBitmap(drawableToBitmap(drawable), width, height, false); + } + public static ColorStateList createBottomNavColorStateList(Context ctx, boolean nightMode) { return AndroidUtils.createCheckedColorStateList(ctx, nightMode, R.color.icon_color_default_light, R.color.wikivoyage_active_light, diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java index 3512de68bc..af4d599afa 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementEditingContext.java @@ -2,9 +2,6 @@ package net.osmand.plus.measurementtool; import android.util.Pair; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import net.osmand.GPXUtilities.GPXFile; import net.osmand.GPXUtilities.TrkSegment; import net.osmand.GPXUtilities.WptPt; @@ -38,12 +35,18 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import static net.osmand.plus.measurementtool.MeasurementEditingContext.CalculationMode.NEXT_SEGMENT; import static net.osmand.plus.measurementtool.MeasurementEditingContext.CalculationMode.WHOLE_TRACK; import static net.osmand.plus.measurementtool.command.MeasurementModeCommand.MeasurementCommandType.APPROXIMATE_POINTS; @@ -1111,8 +1114,26 @@ public class MeasurementEditingContext implements IRouteSettingsListener { return res; } + public boolean isInMultiProfileMode() { + if (lastCalculationMode == CalculationMode.NEXT_SEGMENT) { + return true; + } + Set profiles = new HashSet<>(); + for (RoadSegmentData segmentData : roadSegmentData.values()) { + String profile = segmentData.getAppMode().getStringKey(); + if (!DEFAULT_APP_MODE.getStringKey().equals(profile)) { + profiles.add(profile); + if (profiles.size() >= 2) { + lastCalculationMode = NEXT_SEGMENT; + return true; + } + } + } + return false; + } + @Override public void onRouteSettingsChanged(@Nullable ApplicationMode mode) { recalculateRouteSegments(mode); } -} +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolLayer.java b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolLayer.java index 3c6bcf6c89..9d3f8812c4 100644 --- a/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolLayer.java +++ b/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolLayer.java @@ -23,6 +23,8 @@ import net.osmand.plus.views.OsmandMapTileView; import net.osmand.plus.views.Renderable; import net.osmand.plus.views.layers.ContextMenuLayer; import net.osmand.plus.views.layers.geometry.GeometryWay; +import net.osmand.plus.views.layers.geometry.MultiProfileGeometryWay; +import net.osmand.plus.views.layers.geometry.MultiProfileGeometryWayContext; import net.osmand.util.MapUtils; import java.util.ArrayList; @@ -33,20 +35,28 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL private OsmandMapTileView view; private boolean inMeasurementMode; + private Bitmap centerIconDay; private Bitmap centerIconNight; private Bitmap pointIcon; private Bitmap applyingPointIcon; private Paint bitmapPaint; private final RenderingLineAttributes lineAttrs = new RenderingLineAttributes("measureDistanceLine"); + private final RenderingLineAttributes multiProfileLineAttrs = new RenderingLineAttributes("multiProfileMeasureDistanceLine"); + + private MultiProfileGeometryWay multiProfileGeometry; + private MultiProfileGeometryWayContext multiProfileGeometryWayContext; + private int marginPointIconX; private int marginPointIconY; private int marginApplyingPointIconX; private int marginApplyingPointIconY; private final Path path = new Path(); + private final List tx = new ArrayList<>(); private final List ty = new ArrayList<>(); private OnMeasureDistanceToCenter measureDistanceToCenterListener; + private OnSingleTapListener singleTapListener; private OnEnterMovePointModeListener enterMovePointModeListener; private LatLon pressedPointLatLon; @@ -63,6 +73,19 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL pointIcon = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_measure_point_day); applyingPointIcon = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_measure_point_move_day); + float density = view.getDensity(); + multiProfileLineAttrs.isPaint_1 = false; + multiProfileLineAttrs.paint_1.setColor(0xFFFFFFFF); + multiProfileLineAttrs.paint_1.setStyle(Paint.Style.FILL); + multiProfileLineAttrs.paint.setStrokeWidth(density * 14); + multiProfileLineAttrs.paint2.setStrokeWidth(density * 10); + multiProfileLineAttrs.isPaint3 = false; + multiProfileLineAttrs.paint3.setStrokeWidth(density * 2); + + multiProfileGeometryWayContext = new MultiProfileGeometryWayContext( + view.getContext(), view.getApplication().getUIUtilities(), density); + multiProfileGeometry = new MultiProfileGeometryWay(multiProfileGeometryWayContext); + bitmapPaint = new Paint(); bitmapPaint.setAntiAlias(true); bitmapPaint.setDither(true); @@ -194,15 +217,23 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL } } - List before = editingCtx.getBeforeTrkSegmentLine(); - for (TrkSegment segment : before) { - new Renderable.StandardTrack(new ArrayList<>(segment.points), 17.2). - drawSegment(view.getZoom(), lineAttrs.paint, canvas, tb); - } - List after = editingCtx.getAfterTrkSegmentLine(); - for (TrkSegment segment : after) { - new Renderable.StandardTrack(new ArrayList<>(segment.points), 17.2). - drawSegment(view.getZoom(), lineAttrs.paint, canvas, tb); + if (editingCtx.isInMultiProfileMode()) { + multiProfileGeometryWayContext.updatePaints(settings.isNightMode(), multiProfileLineAttrs); + multiProfileGeometry.updateRoute(tb, editingCtx.getRoadSegmentData(), editingCtx.getBeforeSegments(), editingCtx.getAfterSegments()); + multiProfileGeometry.drawSegments(canvas, tb); + } else { + multiProfileGeometry.clearWay(); + List before = editingCtx.getBeforeTrkSegmentLine(); + for (TrkSegment segment : before) { + new Renderable.StandardTrack(new ArrayList<>(segment.points), 17.2). + drawSegment(view.getZoom(), lineAttrs.paint, canvas, tb); + } + + List after = editingCtx.getAfterTrkSegmentLine(); + for (TrkSegment segment : after) { + new Renderable.StandardTrack(new ArrayList<>(segment.points), 17.2). + drawSegment(view.getZoom(), lineAttrs.paint, canvas, tb); + } } drawPoints(canvas, tb); @@ -237,10 +268,10 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL List beforePoints = editingCtx.getBeforePoints(); List afterPoints = editingCtx.getAfterPoints(); if (beforePoints.size() > 0) { - drawPointIcon(canvas, tb, beforePoints.get(beforePoints.size() - 1)); + drawPointIcon(canvas, tb, beforePoints.get(beforePoints.size() - 1), true); } if (afterPoints.size() > 0) { - drawPointIcon(canvas, tb, afterPoints.get(0)); + drawPointIcon(canvas, tb, afterPoints.get(0), true); } if (editingCtx.getSelectedPointPosition() != -1) { @@ -287,25 +318,13 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL } if (overlapped) { WptPt pt = points.get(0); - if (pt != lastBeforePoint && pt != firstAfterPoint && isInTileBox(tb, pt)) { - float locX = tb.getPixXFromLatLon(pt.lat, pt.lon); - float locY = tb.getPixYFromLatLon(pt.lat, pt.lon); - canvas.drawBitmap(pointIcon, locX - marginPointIconX, locY - marginPointIconY, bitmapPaint); - } + drawPointIcon(canvas, tb, pt, false); pt = points.get(points.size() - 1); - if (pt != lastBeforePoint && pt != firstAfterPoint && isInTileBox(tb, pt)) { - float locX = tb.getPixXFromLatLon(pt.lat, pt.lon); - float locY = tb.getPixYFromLatLon(pt.lat, pt.lon); - canvas.drawBitmap(pointIcon, locX - marginPointIconX, locY - marginPointIconY, bitmapPaint); - } + drawPointIcon(canvas, tb, pt, false); } else { for (int i = 0; i < points.size(); i++) { WptPt pt = points.get(i); - if (pt != lastBeforePoint && pt != firstAfterPoint && isInTileBox(tb, pt)) { - float locX = tb.getPixXFromLatLon(pt.lat, pt.lon); - float locY = tb.getPixYFromLatLon(pt.lat, pt.lon); - canvas.drawBitmap(pointIcon, locX - marginPointIconX, locY - marginPointIconY, bitmapPaint); - } + drawPointIcon(canvas, tb, pt, false); } } @@ -370,14 +389,23 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL canvas.rotate(tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY()); } - private void drawPointIcon(Canvas canvas, RotatedTileBox tb, WptPt pt) { - canvas.rotate(-tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY()); + private void drawPointIcon(Canvas canvas, RotatedTileBox tb, WptPt pt, boolean rotate) { + if (rotate) { + canvas.rotate(-tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY()); + } float locX = tb.getPixXFromLatLon(pt.lat, pt.lon); float locY = tb.getPixYFromLatLon(pt.lat, pt.lon); - if (tb.containsPoint(locX, locY, 0)) { - canvas.drawBitmap(pointIcon, locX - marginPointIconX, locY - marginPointIconY, bitmapPaint); + if (editingCtx.isInMultiProfileMode()) { + canvas.drawBitmap(multiProfileGeometryWayContext.getPointIcon(), locX - multiProfileGeometryWayContext.pointIconSize / 2, + locY - multiProfileGeometryWayContext.pointIconSize / 2, bitmapPaint); + } else { + if (tb.containsPoint(locX, locY, 0)) { + canvas.drawBitmap(pointIcon, locX - marginPointIconX, locY - marginPointIconY, bitmapPaint); + } + } + if (rotate) { + canvas.rotate(tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY()); } - canvas.rotate(tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY()); } public WptPt addCenterPoint(boolean addPointBefore) { @@ -509,4 +537,4 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL interface OnMeasureDistanceToCenter { void onMeasure(float distance, float bearing); } -} +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWay.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWay.java index 9c7fd1c782..ab98990d36 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWay.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWay.java @@ -11,6 +11,8 @@ import net.osmand.Location; import net.osmand.data.RotatedTileBox; import net.osmand.util.MapAlgorithms; import net.osmand.util.MapUtils; +import net.osmand.plus.views.layers.geometry.MultiProfileGeometryWay.GeometryMultiProfileWayStyle; + import java.util.ArrayList; import java.util.Collections; @@ -174,8 +176,8 @@ public abstract class GeometryWay style, List tx, List ty, List angles, List distances, double dist, List> styles) { @@ -333,7 +342,7 @@ public abstract class GeometryWay tx, List ty, + protected void drawRouteSegment(RotatedTileBox tb, Canvas canvas, List tx, List ty, List angles, List distances, double distToFinish, List> styles) { if (tx.size() < 2) { return; @@ -449,4 +458,4 @@ public abstract class GeometryWay { + + private static final String DEFAULT_PROFILE_KEY = ApplicationMode.DEFAULT.getStringKey(); + + private Map, RoadSegmentData> segmentData; + private List beforeSegments; + private List afterSegments; + + public MultiProfileGeometryWay(MultiProfileGeometryWayContext context) { + super(context, new MultiProfileGeometryWayDrawer(context)); + } + + public void drawSegments(Canvas canvas, RotatedTileBox tileBox) { + QuadRect bounds = tileBox.getLatLonBounds(); + drawSegments(tileBox, canvas, bounds.top, bounds.left, bounds.bottom, bounds.right, null, 0); + } + + public void updateRoute(RotatedTileBox tileBox, Map, RoadSegmentData> segmentData, + List beforeSegments, List afterSegments) { + boolean shouldUpdateRoute = tileBox.getMapDensity() != getMapDensity() || segmentDataChanged(segmentData) + || this.beforeSegments != beforeSegments || this.afterSegments != afterSegments || getLocationProvider() == null; + if (shouldUpdateRoute) { + this.segmentData = segmentData; + this.beforeSegments = beforeSegments; + this.afterSegments = afterSegments; + + List locations; + Map> styleMap; + List ways = new ArrayList<>(); + List> styles = new ArrayList<>(); + locations = new ArrayList<>(); + + List allSegments = new ArrayList<>(); + allSegments.addAll(beforeSegments); + allSegments.addAll(afterSegments); + setStyles(allSegments, ways, styles); + + styleMap = new TreeMap<>(); + int i = 0; + int k = 0; + if (ways.size() > 0) { + for (Way w : ways) { + styleMap.put(k, styles.get(i++)); + for (Node n : w.getNodes()) { + Location ln = new Location(""); + ln.setLatitude(n.getLatitude()); + ln.setLongitude(n.getLongitude()); + locations.add(ln); + k++; + } + } + } + + updateWay(locations, styleMap, tileBox); + } + } + + @Override + public void clearWay() { + super.clearWay(); + if (segmentData != null) { + segmentData.clear(); + } + } + + private void setStyles(List segments, List ways, List> styles) { + for (TrkSegment segment : segments) { + List points = segment.points; + for (int i = 0; i < points.size() - 1; i++) { + setStylesInternal(points, i, ways, styles); + } + styles.add(new GeometryMultiProfileWayStyle(getContext(), new ArrayList(), 0, 0, true)); + Way way = new Way(-1); + WptPt last = points.get(points.size() - 1); + way.addNode(new Node(last.lat, last.lon, -1)); + ways.add(way); + } + } + + private void setStylesInternal(List points, int idx, List ways, List> styles) { + WptPt startPt = points.get(idx); + WptPt endPt = points.get(idx + 1); + List routePoints = getRoutePoints(startPt, endPt); + boolean isSecondToLast = idx + 2 == points.size(); + + Way way = new Way(-1); + String currProfileKey = getProfileKey(startPt); + Pair profileData = getProfileData(currProfileKey); + GeometryMultiProfileWayStyle style = new GeometryMultiProfileWayStyle( + getContext(), routePoints, profileData.first, profileData.second); + styles.add(style); + ways.add(way); + + for (LatLon routePt : routePoints) { + if (isSecondToLast || routePt.getLatitude() != endPt.getLatitude() + && routePt.getLongitude() != endPt.getLongitude()) { + way.addNode(new Node(routePt.getLatitude(), routePt.getLongitude(), -1)); + } + } + } + + private List getRoutePoints(WptPt start, WptPt end) { + Pair userLine = new Pair<>(start, end); + RoadSegmentData roadSegmentData = segmentData.get(userLine); + List routePoints = new ArrayList<>(); + + if (roadSegmentData == null || Algorithms.isEmpty(roadSegmentData.getPoints())) { + routePoints.add(new LatLon(start.lat, start.lon)); + routePoints.add(new LatLon(end.lat, end.lon)); + } else { + for (WptPt routePt : roadSegmentData.getPoints()) { + routePoints.add(new LatLon(routePt.lat, routePt.lon)); + } + } + return routePoints; + } + + @Override + protected boolean shouldAddLocation(RotatedTileBox tileBox, double leftLon, double rightLon, + double bottomLat, double topLat, GeometryWayProvider provider, + int currLocationIdx) { + float currX = tileBox.getPixXFromLatLon(provider.getLatitude(currLocationIdx), provider.getLongitude(currLocationIdx)); + float currY = tileBox.getPixYFromLatLon(provider.getLatitude(currLocationIdx), provider.getLongitude(currLocationIdx)); + if (tileBox.containsPoint(currX, currY, getContext().circleSize)) { + return true; + } else if (currLocationIdx + 1 >= provider.getSize()) { + return false; + } + float nextX = tileBox.getPixXFromLatLon(provider.getLatitude(currLocationIdx + 1), provider.getLongitude(currLocationIdx + 1)); + float nextY = tileBox.getPixXFromLatLon(provider.getLatitude(currLocationIdx + 1), provider.getLongitude(currLocationIdx + 1)); + return tileBox.containsPoint(nextX, nextY, getContext().circleSize); + } + + private boolean segmentDataChanged(Map, RoadSegmentData> other) { + if (other.size() != segmentData.size()) { + return true; + } + for (Pair data : other.keySet()) { + if (other.get(data) != segmentData.get(data)) { + return true; + } + } + return false; + } + + @NonNull + private String getProfileKey(WptPt pt) { + String key = pt.getProfileType(); + return key == null ? DEFAULT_PROFILE_KEY : key; + } + + private Pair getProfileData(String profileKey) { + boolean night = getContext().isNightMode(); + ApplicationMode mode = ApplicationMode.valueOfStringKey(profileKey, ApplicationMode.DEFAULT); + return ApplicationMode.DEFAULT.getStringKey().equals(mode.getStringKey()) ? + new Pair<>(ContextCompat.getColor(getContext().getCtx(), ProfileIconColors.DARK_YELLOW.getColor(night)), R.drawable.ic_action_split_interval) : + new Pair<>(mode.getProfileColor(night), mode.getIconRes()); + } + + @NonNull + @Override + public GeometryWayStyle getDefaultWayStyle() { + return null; + } + + public static class GeometryMultiProfileWayStyle extends GeometryWayStyle { + + @ColorInt + private final int lineColor; + @ColorInt + private final int borderColor; + @DrawableRes + private final int profileIconRes; + + private final boolean isGap; + + private final List routePoints; + + public GeometryMultiProfileWayStyle(MultiProfileGeometryWayContext context, List routePoints, + @ColorInt int profileColor, @DrawableRes int profileIconRes, + boolean isGap) { + super(context); + this.routePoints = routePoints; + this.lineColor = profileColor; + this.borderColor = ColorUtils.blendARGB(profileColor, Color.BLACK, 0.2f); + this.profileIconRes = profileIconRes; + this.isGap = isGap; + } + + + public GeometryMultiProfileWayStyle(MultiProfileGeometryWayContext context, List routePoints, + @ColorInt int profileColor, @DrawableRes int profileIconRes) { + this(context, routePoints, profileColor, profileIconRes, false); + } + + @ColorInt + public int getBorderColor() { + return borderColor; + } + + @ColorInt + public int getLineColor() { + return lineColor; + } + + @Override + public Bitmap getPointBitmap() { + return getContext().getProfileIconBitmap(profileIconRes, borderColor); + } + + public List getRoutePoints() { + return routePoints; + } + + public boolean isGap() { + return isGap; + } + + @Override + public boolean equals(Object other) { + return this == other; + } + + @Override + public boolean hasPathLine() { + return true; + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/MultiProfileGeometryWayContext.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/MultiProfileGeometryWayContext.java new file mode 100644 index 0000000000..d6ef3cfa72 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/MultiProfileGeometryWayContext.java @@ -0,0 +1,106 @@ +package net.osmand.plus.views.layers.geometry; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; + +import net.osmand.AndroidUtils; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.views.OsmandMapLayer.RenderingLineAttributes; +import net.osmand.util.Algorithms; + +import java.util.HashMap; +import java.util.Map; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; + +public class MultiProfileGeometryWayContext extends GeometryWayContext { + + private final UiUtilities iconsCache; + + public final float minIconMargin; + public final float circleSize; + public final float pointIconSize; + + private static final String pointColorHex = "#637EFB"; + + private RenderingLineAttributes multiProfileAttrs; + + private Bitmap pointIcon; + private final Map profileIconsBitmapCache; + + public MultiProfileGeometryWayContext(Context ctx, UiUtilities iconsCache, float density) { + super(ctx, density); + this.iconsCache = iconsCache; + profileIconsBitmapCache = new HashMap<>(); + minIconMargin = density * 30; + circleSize = density * 70; + pointIconSize = density * 22f; + } + + public void updatePaints(boolean nightMode, @NonNull RenderingLineAttributes multiProfileAttrs) { + this.multiProfileAttrs = multiProfileAttrs; + super.updatePaints(nightMode, multiProfileAttrs); + } + + @Override + protected void recreateBitmaps() { + float density = getDensity(); + float outerRadius = density * 11f; + float centerRadius = density * 10.5f; + float innerRadius = density * 6.5f; + float centerXY = pointIconSize / 2; + + pointIcon = Bitmap.createBitmap((int) pointIconSize, (int) pointIconSize, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(pointIcon); + Paint paint = new Paint(); + paint.setStyle(Paint.Style.FILL); + + paint.setColor(Color.BLACK); + canvas.drawCircle(centerXY, centerXY, outerRadius, paint); + + paint.setColor(Color.WHITE); + canvas.drawCircle(centerXY, centerXY, centerRadius, paint); + + paint.setColor(Algorithms.parseColor(pointColorHex)); + canvas.drawCircle(centerXY, centerXY, innerRadius, paint); + } + + @NonNull + public Bitmap getProfileIconBitmap(@DrawableRes int iconRes, @ColorInt int color) { + String key = iconRes + "_" + color; + Bitmap bitmap = profileIconsBitmapCache.get(key); + if (bitmap == null) { + bitmap = Bitmap.createBitmap((int) circleSize, (int) circleSize, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + float center = bitmap.getWidth() / 2f; + + canvas.drawCircle(center, center, center / 2, multiProfileAttrs.paint_1); + multiProfileAttrs.paint3.setColor(color); + canvas.drawCircle(center, center, center / 2, multiProfileAttrs.paint3); + + float iconSize = center - getDensity() * 10; + Bitmap profileIconBitmap = AndroidUtils.createScaledBitmap( + iconsCache.getPaintedIcon(iconRes, color), (int) iconSize, (int) iconSize); + canvas.drawBitmap(profileIconBitmap, center - iconSize / 2, center - iconSize / 2, multiProfileAttrs.paint3); + + profileIconsBitmapCache.put(key, bitmap); + } + return bitmap; + } + + @NonNull + public Bitmap getPointIcon() { + return pointIcon; + } + + @Override + protected int getArrowBitmapResId() { + return R.drawable.ic_action_split_interval; + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/views/layers/geometry/MultiProfileGeometryWayDrawer.java b/OsmAnd/src/net/osmand/plus/views/layers/geometry/MultiProfileGeometryWayDrawer.java new file mode 100644 index 0000000000..c9e5f8101f --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/views/layers/geometry/MultiProfileGeometryWayDrawer.java @@ -0,0 +1,84 @@ +package net.osmand.plus.views.layers.geometry; + +import android.graphics.Canvas; +import android.graphics.Path; +import android.graphics.PathMeasure; +import android.graphics.PointF; + +import net.osmand.data.LatLon; +import net.osmand.data.RotatedTileBox; +import net.osmand.plus.views.OsmandMapLayer.RenderingLineAttributes; +import net.osmand.plus.views.layers.geometry.MultiProfileGeometryWay.GeometryMultiProfileWayStyle; +import net.osmand.util.Algorithms; + +import java.util.List; + +public class MultiProfileGeometryWayDrawer extends GeometryWayDrawer { + + public MultiProfileGeometryWayDrawer(MultiProfileGeometryWayContext context) { + super(context); + } + + @Override + public void drawPath(Canvas canvas, Path path, GeometryWayStyle style) { + if (style instanceof GeometryMultiProfileWayStyle && !((GeometryMultiProfileWayStyle) style).isGap()) { + RenderingLineAttributes attrs = getContext().getAttrs(); + + attrs.paint.setColor(((GeometryMultiProfileWayStyle) style).getBorderColor()); + canvas.drawPath(path, attrs.paint); + + attrs.paint2.setColor(((GeometryMultiProfileWayStyle) style).getLineColor()); + canvas.drawPath(path, attrs.paint2); + } + } + + @Override + public void drawArrowsOverPath(Canvas canvas, RotatedTileBox tb, List tx, List ty, List angles, List distances, double distPixToFinish, List> styles) { + Path path = new Path(); + PathMeasure pathMeasure = new PathMeasure(); + MultiProfileGeometryWayContext context = getContext(); + GeometryMultiProfileWayStyle style = null; + + for (int i = 0; i < styles.size(); i++) { + GeometryWayStyle s = styles.get(i); + if (s != null && !s.equals(style) || !((GeometryMultiProfileWayStyle) s).isGap()) { + style = (GeometryMultiProfileWayStyle) styles.get(i); + PointF center = getIconCenter(tb, style.getRoutePoints(), path, pathMeasure); + if (center != null && tb.containsPoint(center.x, center.y, context.circleSize)) { + float x = center.x - context.circleSize / 2; + float y = center.y - context.circleSize / 2; + canvas.drawBitmap(style.getPointBitmap(), x, y, null); + } + } + } + } + + private PointF getIconCenter(RotatedTileBox tileBox, List routePoints, Path path, PathMeasure pathMeasure) { + if (Algorithms.isEmpty(routePoints)) { + return null; + } + + path.reset(); + PointF first = getPoint(tileBox, routePoints.get(0)); + path.moveTo(first.x, first.y); + for (int i = 1; i < routePoints.size(); i++) { + PointF pt = getPoint(tileBox, routePoints.get(i)); + path.lineTo(pt.x, pt.y); + } + + pathMeasure.setPath(path, false); + float routeLength = pathMeasure.getLength(); + if ((routeLength - getContext().circleSize) / 2 < getContext().minIconMargin) { + return null; + } + + float[] xy = new float[2]; + pathMeasure.getPosTan(routeLength * 0.5f, xy, null); + return new PointF(xy[0], xy[1]); + } + + private PointF getPoint(RotatedTileBox tileBox, LatLon latLon) { + return new PointF(tileBox.getPixXFromLatLon(latLon.getLatitude(), latLon.getLongitude()), + tileBox.getPixYFromLatLon(latLon.getLatitude(), latLon.getLongitude())); + } +} \ No newline at end of file