Merge pull request #11320 from osmandapp/ui_multi_profile_plan_route
UI for multi profile plan route
This commit is contained in:
commit
73741c556c
7 changed files with 554 additions and 41 deletions
|
@ -151,6 +151,10 @@ public class AndroidUtils {
|
||||||
return resizedBitmap;
|
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) {
|
public static ColorStateList createBottomNavColorStateList(Context ctx, boolean nightMode) {
|
||||||
return AndroidUtils.createCheckedColorStateList(ctx, nightMode,
|
return AndroidUtils.createCheckedColorStateList(ctx, nightMode,
|
||||||
R.color.icon_color_default_light, R.color.wikivoyage_active_light,
|
R.color.icon_color_default_light, R.color.wikivoyage_active_light,
|
||||||
|
|
|
@ -2,9 +2,6 @@ package net.osmand.plus.measurementtool;
|
||||||
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import net.osmand.GPXUtilities.GPXFile;
|
import net.osmand.GPXUtilities.GPXFile;
|
||||||
import net.osmand.GPXUtilities.TrkSegment;
|
import net.osmand.GPXUtilities.TrkSegment;
|
||||||
import net.osmand.GPXUtilities.WptPt;
|
import net.osmand.GPXUtilities.WptPt;
|
||||||
|
@ -38,12 +35,18 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
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.MeasurementEditingContext.CalculationMode.WHOLE_TRACK;
|
||||||
import static net.osmand.plus.measurementtool.command.MeasurementModeCommand.MeasurementCommandType.APPROXIMATE_POINTS;
|
import static net.osmand.plus.measurementtool.command.MeasurementModeCommand.MeasurementCommandType.APPROXIMATE_POINTS;
|
||||||
|
|
||||||
|
@ -1111,6 +1114,24 @@ public class MeasurementEditingContext implements IRouteSettingsListener {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isInMultiProfileMode() {
|
||||||
|
if (lastCalculationMode == CalculationMode.NEXT_SEGMENT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Set<String> 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
|
@Override
|
||||||
public void onRouteSettingsChanged(@Nullable ApplicationMode mode) {
|
public void onRouteSettingsChanged(@Nullable ApplicationMode mode) {
|
||||||
recalculateRouteSegments(mode);
|
recalculateRouteSegments(mode);
|
||||||
|
|
|
@ -23,6 +23,8 @@ import net.osmand.plus.views.OsmandMapTileView;
|
||||||
import net.osmand.plus.views.Renderable;
|
import net.osmand.plus.views.Renderable;
|
||||||
import net.osmand.plus.views.layers.ContextMenuLayer;
|
import net.osmand.plus.views.layers.ContextMenuLayer;
|
||||||
import net.osmand.plus.views.layers.geometry.GeometryWay;
|
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 net.osmand.util.MapUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -33,20 +35,28 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL
|
||||||
|
|
||||||
private OsmandMapTileView view;
|
private OsmandMapTileView view;
|
||||||
private boolean inMeasurementMode;
|
private boolean inMeasurementMode;
|
||||||
|
|
||||||
private Bitmap centerIconDay;
|
private Bitmap centerIconDay;
|
||||||
private Bitmap centerIconNight;
|
private Bitmap centerIconNight;
|
||||||
private Bitmap pointIcon;
|
private Bitmap pointIcon;
|
||||||
private Bitmap applyingPointIcon;
|
private Bitmap applyingPointIcon;
|
||||||
private Paint bitmapPaint;
|
private Paint bitmapPaint;
|
||||||
private final RenderingLineAttributes lineAttrs = new RenderingLineAttributes("measureDistanceLine");
|
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 marginPointIconX;
|
||||||
private int marginPointIconY;
|
private int marginPointIconY;
|
||||||
private int marginApplyingPointIconX;
|
private int marginApplyingPointIconX;
|
||||||
private int marginApplyingPointIconY;
|
private int marginApplyingPointIconY;
|
||||||
private final Path path = new Path();
|
private final Path path = new Path();
|
||||||
|
|
||||||
private final List<Float> tx = new ArrayList<>();
|
private final List<Float> tx = new ArrayList<>();
|
||||||
private final List<Float> ty = new ArrayList<>();
|
private final List<Float> ty = new ArrayList<>();
|
||||||
private OnMeasureDistanceToCenter measureDistanceToCenterListener;
|
private OnMeasureDistanceToCenter measureDistanceToCenterListener;
|
||||||
|
|
||||||
private OnSingleTapListener singleTapListener;
|
private OnSingleTapListener singleTapListener;
|
||||||
private OnEnterMovePointModeListener enterMovePointModeListener;
|
private OnEnterMovePointModeListener enterMovePointModeListener;
|
||||||
private LatLon pressedPointLatLon;
|
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);
|
pointIcon = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_measure_point_day);
|
||||||
applyingPointIcon = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_measure_point_move_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 = new Paint();
|
||||||
bitmapPaint.setAntiAlias(true);
|
bitmapPaint.setAntiAlias(true);
|
||||||
bitmapPaint.setDither(true);
|
bitmapPaint.setDither(true);
|
||||||
|
@ -194,16 +217,24 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<TrkSegment> before = editingCtx.getBeforeTrkSegmentLine();
|
List<TrkSegment> before = editingCtx.getBeforeTrkSegmentLine();
|
||||||
for (TrkSegment segment : before) {
|
for (TrkSegment segment : before) {
|
||||||
new Renderable.StandardTrack(new ArrayList<>(segment.points), 17.2).
|
new Renderable.StandardTrack(new ArrayList<>(segment.points), 17.2).
|
||||||
drawSegment(view.getZoom(), lineAttrs.paint, canvas, tb);
|
drawSegment(view.getZoom(), lineAttrs.paint, canvas, tb);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TrkSegment> after = editingCtx.getAfterTrkSegmentLine();
|
List<TrkSegment> after = editingCtx.getAfterTrkSegmentLine();
|
||||||
for (TrkSegment segment : after) {
|
for (TrkSegment segment : after) {
|
||||||
new Renderable.StandardTrack(new ArrayList<>(segment.points), 17.2).
|
new Renderable.StandardTrack(new ArrayList<>(segment.points), 17.2).
|
||||||
drawSegment(view.getZoom(), lineAttrs.paint, canvas, tb);
|
drawSegment(view.getZoom(), lineAttrs.paint, canvas, tb);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
drawPoints(canvas, tb);
|
drawPoints(canvas, tb);
|
||||||
}
|
}
|
||||||
|
@ -237,10 +268,10 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL
|
||||||
List<WptPt> beforePoints = editingCtx.getBeforePoints();
|
List<WptPt> beforePoints = editingCtx.getBeforePoints();
|
||||||
List<WptPt> afterPoints = editingCtx.getAfterPoints();
|
List<WptPt> afterPoints = editingCtx.getAfterPoints();
|
||||||
if (beforePoints.size() > 0) {
|
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) {
|
if (afterPoints.size() > 0) {
|
||||||
drawPointIcon(canvas, tb, afterPoints.get(0));
|
drawPointIcon(canvas, tb, afterPoints.get(0), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editingCtx.getSelectedPointPosition() != -1) {
|
if (editingCtx.getSelectedPointPosition() != -1) {
|
||||||
|
@ -287,25 +318,13 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL
|
||||||
}
|
}
|
||||||
if (overlapped) {
|
if (overlapped) {
|
||||||
WptPt pt = points.get(0);
|
WptPt pt = points.get(0);
|
||||||
if (pt != lastBeforePoint && pt != firstAfterPoint && isInTileBox(tb, pt)) {
|
drawPointIcon(canvas, tb, pt, false);
|
||||||
float locX = tb.getPixXFromLatLon(pt.lat, pt.lon);
|
|
||||||
float locY = tb.getPixYFromLatLon(pt.lat, pt.lon);
|
|
||||||
canvas.drawBitmap(pointIcon, locX - marginPointIconX, locY - marginPointIconY, bitmapPaint);
|
|
||||||
}
|
|
||||||
pt = points.get(points.size() - 1);
|
pt = points.get(points.size() - 1);
|
||||||
if (pt != lastBeforePoint && pt != firstAfterPoint && isInTileBox(tb, pt)) {
|
drawPointIcon(canvas, tb, pt, false);
|
||||||
float locX = tb.getPixXFromLatLon(pt.lat, pt.lon);
|
|
||||||
float locY = tb.getPixYFromLatLon(pt.lat, pt.lon);
|
|
||||||
canvas.drawBitmap(pointIcon, locX - marginPointIconX, locY - marginPointIconY, bitmapPaint);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < points.size(); i++) {
|
for (int i = 0; i < points.size(); i++) {
|
||||||
WptPt pt = points.get(i);
|
WptPt pt = points.get(i);
|
||||||
if (pt != lastBeforePoint && pt != firstAfterPoint && isInTileBox(tb, pt)) {
|
drawPointIcon(canvas, tb, pt, false);
|
||||||
float locX = tb.getPixXFromLatLon(pt.lat, pt.lon);
|
|
||||||
float locY = tb.getPixYFromLatLon(pt.lat, pt.lon);
|
|
||||||
canvas.drawBitmap(pointIcon, locX - marginPointIconX, locY - marginPointIconY, bitmapPaint);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,15 +389,24 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL
|
||||||
canvas.rotate(tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
|
canvas.rotate(tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawPointIcon(Canvas canvas, RotatedTileBox tb, WptPt pt) {
|
private void drawPointIcon(Canvas canvas, RotatedTileBox tb, WptPt pt, boolean rotate) {
|
||||||
|
if (rotate) {
|
||||||
canvas.rotate(-tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
|
canvas.rotate(-tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
|
||||||
|
}
|
||||||
float locX = tb.getPixXFromLatLon(pt.lat, pt.lon);
|
float locX = tb.getPixXFromLatLon(pt.lat, pt.lon);
|
||||||
float locY = tb.getPixYFromLatLon(pt.lat, pt.lon);
|
float locY = tb.getPixYFromLatLon(pt.lat, pt.lon);
|
||||||
|
if (editingCtx.isInMultiProfileMode()) {
|
||||||
|
canvas.drawBitmap(multiProfileGeometryWayContext.getPointIcon(), locX - multiProfileGeometryWayContext.pointIconSize / 2,
|
||||||
|
locY - multiProfileGeometryWayContext.pointIconSize / 2, bitmapPaint);
|
||||||
|
} else {
|
||||||
if (tb.containsPoint(locX, locY, 0)) {
|
if (tb.containsPoint(locX, locY, 0)) {
|
||||||
canvas.drawBitmap(pointIcon, locX - marginPointIconX, locY - marginPointIconY, bitmapPaint);
|
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) {
|
public WptPt addCenterPoint(boolean addPointBefore) {
|
||||||
RotatedTileBox tb = view.getCurrentRotatedTileBox();
|
RotatedTileBox tb = view.getCurrentRotatedTileBox();
|
||||||
|
|
|
@ -11,6 +11,8 @@ import net.osmand.Location;
|
||||||
import net.osmand.data.RotatedTileBox;
|
import net.osmand.data.RotatedTileBox;
|
||||||
import net.osmand.util.MapAlgorithms;
|
import net.osmand.util.MapAlgorithms;
|
||||||
import net.osmand.util.MapUtils;
|
import net.osmand.util.MapUtils;
|
||||||
|
import net.osmand.plus.views.layers.geometry.MultiProfileGeometryWay.GeometryMultiProfileWayStyle;
|
||||||
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -174,8 +176,8 @@ public abstract class GeometryWay<T extends GeometryWayContext, D extends Geomet
|
||||||
}
|
}
|
||||||
double lat = locationProvider.getLatitude(i);
|
double lat = locationProvider.getLatitude(i);
|
||||||
double lon = locationProvider.getLongitude(i);
|
double lon = locationProvider.getLongitude(i);
|
||||||
if (leftLongitude <= lon && lon <= rightLongitude && bottomLatitude <= lat
|
if (shouldAddLocation(tb, leftLongitude, rightLongitude, bottomLatitude, topLatitude,
|
||||||
&& lat <= topLatitude) {
|
locationProvider, i)) {
|
||||||
double dist = previous == -1 ? 0 : odistances.get(i);
|
double dist = previous == -1 ? 0 : odistances.get(i);
|
||||||
if (!previousVisible) {
|
if (!previousVisible) {
|
||||||
double prevLat = Double.NaN;
|
double prevLat = Double.NaN;
|
||||||
|
@ -188,7 +190,7 @@ public abstract class GeometryWay<T extends GeometryWayContext, D extends Geomet
|
||||||
prevLon = lastProjection.getLongitude();
|
prevLon = lastProjection.getLongitude();
|
||||||
}
|
}
|
||||||
if (!Double.isNaN(prevLat) && !Double.isNaN(prevLon)) {
|
if (!Double.isNaN(prevLat) && !Double.isNaN(prevLon)) {
|
||||||
addLocation(tb, prevLat, prevLon, style, tx, ty, angles, distances, dist, styles); // first point
|
addLocation(tb, prevLat, prevLon, getStyle(i - 1, style), tx, ty, angles, distances, dist, styles); // first point
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addLocation(tb, lat, lon, style, tx, ty, angles, distances, dist, styles);
|
addLocation(tb, lat, lon, style, tx, ty, angles, distances, dist, styles);
|
||||||
|
@ -208,6 +210,13 @@ public abstract class GeometryWay<T extends GeometryWayContext, D extends Geomet
|
||||||
drawRouteSegment(tb, canvas, tx, ty, angles, distances, 0, styles);
|
drawRouteSegment(tb, canvas, tx, ty, angles, distances, 0, styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean shouldAddLocation(RotatedTileBox tileBox, double leftLon, double rightLon, double bottomLat,
|
||||||
|
double topLat, GeometryWayProvider provider, int currLocationIdx) {
|
||||||
|
double lat = provider.getLatitude(currLocationIdx);
|
||||||
|
double lon = provider.getLongitude(currLocationIdx);
|
||||||
|
return leftLon <= lon && lon <= rightLon && bottomLat <= lat && lat <= topLat;
|
||||||
|
}
|
||||||
|
|
||||||
private void addLocation(RotatedTileBox tb, double latitude, double longitude, GeometryWayStyle<?> style,
|
private void addLocation(RotatedTileBox tb, double latitude, double longitude, GeometryWayStyle<?> style,
|
||||||
List<Float> tx, List<Float> ty, List<Double> angles, List<Double> distances,
|
List<Float> tx, List<Float> ty, List<Double> angles, List<Double> distances,
|
||||||
double dist, List<GeometryWayStyle<?>> styles) {
|
double dist, List<GeometryWayStyle<?>> styles) {
|
||||||
|
@ -333,7 +342,7 @@ public abstract class GeometryWay<T extends GeometryWayContext, D extends Geomet
|
||||||
return cnt;
|
return cnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawRouteSegment(RotatedTileBox tb, Canvas canvas, List<Float> tx, List<Float> ty,
|
protected void drawRouteSegment(RotatedTileBox tb, Canvas canvas, List<Float> tx, List<Float> ty,
|
||||||
List<Double> angles, List<Double> distances, double distToFinish, List<GeometryWayStyle<?>> styles) {
|
List<Double> angles, List<Double> distances, double distToFinish, List<GeometryWayStyle<?>> styles) {
|
||||||
if (tx.size() < 2) {
|
if (tx.size() < 2) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -0,0 +1,261 @@
|
||||||
|
package net.osmand.plus.views.layers.geometry;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import net.osmand.GPXUtilities.TrkSegment;
|
||||||
|
import net.osmand.GPXUtilities.WptPt;
|
||||||
|
import net.osmand.Location;
|
||||||
|
import net.osmand.data.LatLon;
|
||||||
|
import net.osmand.data.QuadRect;
|
||||||
|
import net.osmand.data.RotatedTileBox;
|
||||||
|
import net.osmand.osm.edit.Node;
|
||||||
|
import net.osmand.osm.edit.Way;
|
||||||
|
import net.osmand.plus.R;
|
||||||
|
import net.osmand.plus.measurementtool.RoadSegmentData;
|
||||||
|
import net.osmand.plus.profiles.ProfileIconColors;
|
||||||
|
import net.osmand.plus.settings.backend.ApplicationMode;
|
||||||
|
import net.osmand.util.Algorithms;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.graphics.ColorUtils;
|
||||||
|
|
||||||
|
public class MultiProfileGeometryWay extends GeometryWay<MultiProfileGeometryWayContext, MultiProfileGeometryWayDrawer> {
|
||||||
|
|
||||||
|
private static final String DEFAULT_PROFILE_KEY = ApplicationMode.DEFAULT.getStringKey();
|
||||||
|
|
||||||
|
private Map<Pair<WptPt, WptPt>, RoadSegmentData> segmentData;
|
||||||
|
private List<TrkSegment> beforeSegments;
|
||||||
|
private List<TrkSegment> 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<Pair<WptPt, WptPt>, RoadSegmentData> segmentData,
|
||||||
|
List<TrkSegment> beforeSegments, List<TrkSegment> 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<Location> locations;
|
||||||
|
Map<Integer, GeometryWayStyle<?>> styleMap;
|
||||||
|
List<Way> ways = new ArrayList<>();
|
||||||
|
List<GeometryWayStyle<?>> styles = new ArrayList<>();
|
||||||
|
locations = new ArrayList<>();
|
||||||
|
|
||||||
|
List<TrkSegment> 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<TrkSegment> segments, List<Way> ways, List<GeometryWayStyle<?>> styles) {
|
||||||
|
for (TrkSegment segment : segments) {
|
||||||
|
List<WptPt> points = segment.points;
|
||||||
|
for (int i = 0; i < points.size() - 1; i++) {
|
||||||
|
setStylesInternal(points, i, ways, styles);
|
||||||
|
}
|
||||||
|
styles.add(new GeometryMultiProfileWayStyle(getContext(), new ArrayList<LatLon>(), 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<WptPt> points, int idx, List<Way> ways, List<GeometryWayStyle<?>> styles) {
|
||||||
|
WptPt startPt = points.get(idx);
|
||||||
|
WptPt endPt = points.get(idx + 1);
|
||||||
|
List<LatLon> routePoints = getRoutePoints(startPt, endPt);
|
||||||
|
boolean isSecondToLast = idx + 2 == points.size();
|
||||||
|
|
||||||
|
Way way = new Way(-1);
|
||||||
|
String currProfileKey = getProfileKey(startPt);
|
||||||
|
Pair<Integer, Integer> 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<LatLon> getRoutePoints(WptPt start, WptPt end) {
|
||||||
|
Pair<WptPt, WptPt> userLine = new Pair<>(start, end);
|
||||||
|
RoadSegmentData roadSegmentData = segmentData.get(userLine);
|
||||||
|
List<LatLon> 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<Pair<WptPt, WptPt>, RoadSegmentData> other) {
|
||||||
|
if (other.size() != segmentData.size()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (Pair<WptPt, WptPt> 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<Integer, Integer> 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<MultiProfileGeometryWayContext> {
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
private final int lineColor;
|
||||||
|
@ColorInt
|
||||||
|
private final int borderColor;
|
||||||
|
@DrawableRes
|
||||||
|
private final int profileIconRes;
|
||||||
|
|
||||||
|
private final boolean isGap;
|
||||||
|
|
||||||
|
private final List<LatLon> routePoints;
|
||||||
|
|
||||||
|
public GeometryMultiProfileWayStyle(MultiProfileGeometryWayContext context, List<LatLon> 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<LatLon> 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<LatLon> getRoutePoints() {
|
||||||
|
return routePoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGap() {
|
||||||
|
return isGap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return this == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPathLine() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, Bitmap> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<MultiProfileGeometryWayContext> {
|
||||||
|
|
||||||
|
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<Float> tx, List<Float> ty, List<Double> angles, List<Double> distances, double distPixToFinish, List<GeometryWayStyle<?>> 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<LatLon> 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()));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue