Draw profile icon on line center

This commit is contained in:
cepprice 2021-04-01 22:39:33 +05:00
parent 762e574bf0
commit bf55e86a7e
6 changed files with 144 additions and 63 deletions

View file

@ -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,

View file

@ -82,7 +82,8 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL
multiProfileLineAttrs.isPaint3 = false;
multiProfileLineAttrs.paint3.setStrokeWidth(density * 2);
multiProfileGeometryWayContext = new MultiProfileGeometryWayContext(view.getContext(), density);
multiProfileGeometryWayContext = new MultiProfileGeometryWayContext(
view.getContext(), view.getApplication().getUIUtilities(), density);
multiProfileGeometry = new MultiProfileGeometryWay(multiProfileGeometryWayContext);
bitmapPaint = new Paint();
@ -227,6 +228,7 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL
multiProfileGeometry.drawSegments(canvas, tb);
}
} else {
multiProfileGeometry.clearWay();
List<TrkSegment> before = editingCtx.getBeforeTrkSegmentLine();
for (TrkSegment segment : before) {
new Renderable.StandardTrack(new ArrayList<>(segment.points), 17.2).

View file

@ -174,7 +174,7 @@ public abstract class GeometryWay<T extends GeometryWayContext, D extends Geomet
}
double lat = locationProvider.getLatitude(i);
double lon = locationProvider.getLongitude(i);
if (shouldAddLocation(leftLongitude, rightLongitude, bottomLatitude, topLatitude,
if (shouldAddLocation(tb, leftLongitude, rightLongitude, bottomLatitude, topLatitude,
locationProvider, i)) {
double dist = previous == -1 ? 0 : odistances.get(i);
if (!previousVisible) {
@ -188,7 +188,7 @@ public abstract class GeometryWay<T extends GeometryWayContext, D extends Geomet
prevLon = lastProjection.getLongitude();
}
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, defaultWayStyle), tx, ty, angles, distances, dist, styles); // first point
}
}
addLocation(tb, lat, lon, style, tx, ty, angles, distances, dist, styles);
@ -208,8 +208,8 @@ public abstract class GeometryWay<T extends GeometryWayContext, D extends Geomet
drawRouteSegment(tb, canvas, tx, ty, angles, distances, 0, styles);
}
protected boolean shouldAddLocation(double leftLon, double rightLon, double bottomLat, double topLat,
GeometryWayProvider provider, int currLocationIdx) {
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;
@ -261,16 +261,20 @@ public abstract class GeometryWay<T extends GeometryWayContext, D extends Geomet
return x >= lx && x <= rx && y >= ty && y <= by;
}
public static boolean isIn(float x, float y, int lx, int ty, int rx, int by, float outMargin) {
return x >= lx - outMargin && x <= rx + outMargin && y >= ty - outMargin && y <= by + outMargin;
}
public static int calculatePath(RotatedTileBox tb, List<Float> xs, List<Float> ys, Path path) {
List<Pair<Path, GeometryWayStyle<?>>> paths = new ArrayList<>();
int res = calculatePath(tb, xs, ys, null, paths);
int res = calculatePath(tb, xs, ys, 0, null, paths);
if (paths.size() > 0) {
path.addPath(paths.get(0).first);
}
return res;
}
public static int calculatePath(RotatedTileBox tb, List<Float> xs, List<Float> ys, List<GeometryWayStyle<?>> styles, List<Pair<Path, GeometryWayStyle<?>>> paths) {
public static int calculatePath(RotatedTileBox tb, List<Float> xs, List<Float> ys, float outMargin, List<GeometryWayStyle<?>> styles, List<Pair<Path, GeometryWayStyle<?>>> paths) {
boolean segmentStarted = false;
float prevX = xs.get(0);
float prevY = ys.get(0);
@ -280,11 +284,11 @@ public abstract class GeometryWay<T extends GeometryWayContext, D extends Geomet
boolean hasStyles = styles != null && styles.size() == xs.size();
GeometryWayStyle<?> style = hasStyles ? styles.get(0) : null;
Path path = new Path();
boolean prevIn = isIn(prevX, prevY, 0, 0, width, height);
boolean prevIn = isIn(prevX, prevY, 0, 0, width, height, outMargin);
for (int i = 1; i < xs.size(); i++) {
float currX = xs.get(i);
float currY = ys.get(i);
boolean currIn = isIn(currX, currY, 0, 0, width, height);
boolean currIn = isIn(currX, currY, 0, 0, width, height, outMargin);
boolean draw = false;
if (prevIn && currIn) {
draw = true;
@ -356,7 +360,7 @@ public abstract class GeometryWay<T extends GeometryWayContext, D extends Geomet
if (hasPathLine) {
List<Pair<Path, GeometryWayStyle<?>>> paths = new ArrayList<>();
canvas.rotate(-tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
calculatePath(tb, tx, ty, styles, paths);
calculatePath(tb, tx, ty, 0, styles, paths);
for (Pair<Path, GeometryWayStyle<?>> pc : paths) {
GeometryWayStyle<?> style = pc.second;
if (style.hasPathLine()) {

View file

@ -4,6 +4,7 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.Pair;
import net.osmand.GPXUtilities.TrkSegment;
@ -55,14 +56,14 @@ public class MultiProfileGeometryWay extends GeometryWay<MultiProfileGeometryWay
try {
List<Pair<Path, GeometryWayStyle<?>>> pathStyles = new ArrayList<>();
canvas.rotate(-tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
calculatePath(tb, tx, ty, styles, pathStyles);
calculatePath(tb, tx, ty, getContext().circleSize, styles, pathStyles);
for (int i = 0; i < pathStyles.size(); i++) {
Pair<Path, GeometryWayStyle<?>> currPathStyle = pathStyles.get(i);
getDrawer().drawPathBorder(canvas, currPathStyle.first, currPathStyle.second);
getDrawer().drawPath(canvas, currPathStyle.first, currPathStyle.second);
}
// drawer.drawArrowsOverPath(canvas, tb, tx, ty, angles, distances, distToFinish, styles);
getDrawer().drawArrowsOverPath(canvas, tb, tx, ty, angles, distances, distToFinish, styles);
} finally {
canvas.rotate(tb.getRotate(), tb.getCenterPixelX(), tb.getCenterPixelY());
}
@ -71,8 +72,8 @@ public class MultiProfileGeometryWay extends GeometryWay<MultiProfileGeometryWay
public void updateRoute(RotatedTileBox tileBox, Map<Pair<WptPt, WptPt>, RoadSegmentData> segmentData,
boolean before, List<TrkSegment> segments, int segmentIdx) {
boolean shouldUpdateRoute = tileBox.getMapDensity() != getMapDensity() || segmentDataChanged(segmentData)
|| getSegments(before) != segments || true;
if (shouldUpdateRoute && segments.get(segmentIdx).points.size() >= 2) {
|| getSegments(before) != segments || getLocationProvider() == null;
if (shouldUpdateRoute) {
this.segmentData = segmentData;
setSegments(before, segments);
List<WptPt> userPoints = segments.get(segmentIdx).points;
@ -81,7 +82,7 @@ public class MultiProfileGeometryWay extends GeometryWay<MultiProfileGeometryWay
List<Way> ways = new ArrayList<>();
List<GeometryWayStyle<?>> styles = new ArrayList<>();
setStyles(userPoints, ways, styles);
setStyles(tileBox, userPoints, ways, styles);
locations = new ArrayList<>();
styleMap = new TreeMap<>();
@ -104,45 +105,68 @@ public class MultiProfileGeometryWay extends GeometryWay<MultiProfileGeometryWay
}
}
private void setStyles(List<WptPt> userPoints, List<Way> ways, List<GeometryWayStyle<?>> styles) {
String prevProfileKey = "";
Way way = new Way(-2);
private void setStyles(RotatedTileBox tileBox, List<WptPt> userPoints, List<Way> ways, List<GeometryWayStyle<?>> styles) {
MultiProfileGeometryWayContext context = getContext();
Path path = new Path();
PathMeasure pathMeasure = new PathMeasure();
for (int i = 0; i < userPoints.size() - 1; i++) {
WptPt leftPt = userPoints.get(i);
Pair<WptPt, WptPt> userLine = new Pair<>(leftPt, userPoints.get(i + 1));
RoadSegmentData routeBetweenPoints = segmentData.get(userLine);
if (!prevProfileKey.equals(getProfileKey(leftPt)) && !leftPt.isGap()) {
way = new Way(-2);
String currProfileKey = getProfileKey(leftPt);
Pair<Integer, Integer> profileData = getProfileData(currProfileKey);
styles.add(new GeometryMultiProfileWayStyle(getContext(), profileData.first, profileData.second));
ways.add(way);
prevProfileKey = currProfileKey;
}
Way way = new Way(-1);
String currProfileKey = getProfileKey(leftPt);
Pair<Integer, Integer> profileData = getProfileData(currProfileKey);
GeometryMultiProfileWayStyle style = new GeometryMultiProfileWayStyle(
getContext(), currProfileKey, profileData.first, profileData.second);
styles.add(style);
ways.add(way);
path.reset();
boolean isSecondToLast = i + 2 == userPoints.size();
if (routeBetweenPoints == null || Algorithms.isEmpty(routeBetweenPoints.getPoints())) {
way.addNode(new Node(userLine.first.lat, userLine.first.lon, -1));
if (isSecondToLast) {
way.addNode(new Node(userLine.second.lat, userLine.second.lon, -1));
}
movePathToWpt(path, tileBox, userLine.first);
pathLineToWpt(path, tileBox, userLine.second);
} else {
movePathToWpt(path, tileBox, routeBetweenPoints.getPoints().get(0));
for (WptPt pt : routeBetweenPoints.getPoints()) {
if (pt.lat != userLine.second.lat && pt.lon != userLine.second.lon || isSecondToLast) {
way.addNode(new Node(pt.lat, pt.lon, -1));
}
pathLineToWpt(path, tileBox, pt);
}
}
float[] xy = new float[2];
pathMeasure.setPath(path, false);
float routeLength = pathMeasure.getLength();
if ((routeLength - context.circleSize) / 2 >= context.minIconMargin) {
pathMeasure.getPosTan(pathMeasure.getLength() * 0.5f, xy, null);
style.setIconLat(tileBox.getLatFromPixel(xy[0], xy[1]));
style.setIconLon(tileBox.getLonFromPixel(xy[0], xy[1]));
}
}
}
@Override
protected boolean shouldAddLocation(double leftLon, double rightLon, double bottomLat, double topLat, GeometryWayProvider provider, int currLocationIdx) {
return super.shouldAddLocation(leftLon, rightLon, bottomLat, topLat, provider, currLocationIdx)
|| currLocationIdx + 1 < provider.getSize()
&& super.shouldAddLocation(leftLon, rightLon, bottomLat, topLat, provider, currLocationIdx + 1);
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) {
@ -169,6 +193,14 @@ public class MultiProfileGeometryWay extends GeometryWay<MultiProfileGeometryWay
return before ? this.beforeSegments : this.afterSegments;
}
private void movePathToWpt(Path path, RotatedTileBox tileBox, WptPt pt) {
path.moveTo(tileBox.getPixXFromLatLon(pt.lat, pt.lon), tileBox.getPixYFromLatLon(pt.lat, pt.lon));
}
private void pathLineToWpt(Path path, RotatedTileBox tileBox, WptPt pt) {
path.lineTo(tileBox.getPixXFromLatLon(pt.lat, pt.lon), tileBox.getPixYFromLatLon(pt.lat, pt.lon));
}
@NonNull
private String getProfileKey(WptPt pt) {
String key = pt.getProfileType();
@ -191,6 +223,7 @@ public class MultiProfileGeometryWay extends GeometryWay<MultiProfileGeometryWay
public static class GeometryMultiProfileWayStyle extends GeometryWayStyle<MultiProfileGeometryWayContext> {
private final String profileKey;
@ColorInt
private final int lineColor;
@ColorInt
@ -198,9 +231,13 @@ public class MultiProfileGeometryWay extends GeometryWay<MultiProfileGeometryWay
@DrawableRes
private final int profileIconRes;
public GeometryMultiProfileWayStyle(MultiProfileGeometryWayContext context,
private double iconLat;
private double iconLon;
public GeometryMultiProfileWayStyle(MultiProfileGeometryWayContext context, String profileKey,
@ColorInt int profileColor, @DrawableRes int profileIconRes) {
super(context);
this.profileKey = profileKey;
this.lineColor = profileColor;
this.borderColor = ColorUtils.blendARGB(profileColor, Color.BLACK, 0.2f);
this.profileIconRes = profileIconRes;
@ -216,32 +253,30 @@ public class MultiProfileGeometryWay extends GeometryWay<MultiProfileGeometryWay
return lineColor;
}
@DrawableRes
public int getProfileIconRes() {
return profileIconRes;
}
@Override
public Bitmap getPointBitmap() {
// return getContext().getProfileIconBitmap();
return null;
return getContext().getProfileIconBitmap(profileKey, profileIconRes, borderColor);
}
public void setIconLat(double lat) {
iconLat = lat;
}
public void setIconLon(double lon) {
iconLon = lon;
}
public double getIconLat() {
return iconLat;
}
public double getIconLon() {
return iconLon;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
if (!super.equals(other)) {
return false;
}
GeometryMultiProfileWayStyle that = (GeometryMultiProfileWayStyle) other;
return lineColor == that.lineColor &&
borderColor == that.borderColor &&
profileIconRes == that.profileIconRes;
return this == other;
}
@Override

View file

@ -6,25 +6,36 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import net.osmand.AndroidUtils;
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;
private RenderingLineAttributes multiProfileAttrs;
private Bitmap pointIcon;
private final Map<String, Bitmap> profileIconsBitmapCache;
public MultiProfileGeometryWayContext(Context ctx, float density) {
public MultiProfileGeometryWayContext(Context ctx, UiUtilities iconsCache, float density) {
super(ctx, density);
this.iconsCache = iconsCache;
profileIconsBitmapCache = new HashMap<>();
minIconMargin = density * 30;
circleSize = density * 70;
}
public void updatePaints(boolean nightMode, @NonNull RenderingLineAttributes multiProfileAttrs) {
@ -62,18 +73,24 @@ public class MultiProfileGeometryWayContext extends GeometryWayContext {
}
@NonNull
public Bitmap getProfileIconBitmap(@NonNull String profileKey, int profileColor) {
String key = profileKey + "_" + profileColor;
public Bitmap getProfileIconBitmap(String profileKey, @DrawableRes int iconRes, @ColorInt int color) {
String key = profileKey + "_" + iconRes + "_" + color;
Bitmap bitmap = profileIconsBitmapCache.get(key);
if (bitmap == null) {
float density = getDensity();
float diameter = density * 18;
bitmap = Bitmap.createBitmap((int) diameter, (int) diameter, Bitmap.Config.ARGB_8888);
bitmap = Bitmap.createBitmap((int) circleSize, (int) circleSize, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawCircle(diameter / 2, diameter / 2, diameter / 2, multiProfileAttrs.paint_1);
multiProfileAttrs.paint3.setColor(profileColor);
canvas.drawCircle(diameter / 2, diameter / 2, diameter / 2, multiProfileAttrs.paint3);
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;
}

View file

@ -3,9 +3,11 @@ package net.osmand.plus.views.layers.geometry;
import android.graphics.Canvas;
import android.graphics.Path;
import net.osmand.data.RotatedTileBox;
import net.osmand.plus.views.OsmandMapLayer.RenderingLineAttributes;
import net.osmand.plus.views.layers.geometry.MultiProfileGeometryWay.GeometryMultiProfileWayStyle;
import net.osmand.plus.views.OsmandMapLayer.RenderingLineAttributes;
import java.util.List;
public class MultiProfileGeometryWayDrawer extends GeometryWayDrawer<MultiProfileGeometryWayContext> {
@ -22,6 +24,23 @@ public class MultiProfileGeometryWayDrawer extends GeometryWayDrawer<MultiProfil
}
}
@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) {
MultiProfileGeometryWayContext context = getContext();
GeometryMultiProfileWayStyle style = null;
for (int i = 0; i < styles.size(); i++) {
if (styles.get(i) != null && !styles.get(i).equals(style)) {
style = (GeometryMultiProfileWayStyle) styles.get(i);
double lat = style.getIconLat();
double lon = style.getIconLon();
float x = tb.getPixXFromLatLon(lat, lon) - context.circleSize / 2;
float y = tb.getPixYFromLatLon(lat, lon) - context.circleSize / 2;
canvas.drawBitmap(style.getPointBitmap(), x, y, null);
}
}
}
public void drawPathBorder(Canvas canvas, Path path, GeometryWayStyle<?> style) {
if (style instanceof GeometryMultiProfileWayStyle) {
RenderingLineAttributes attrs = getContext().getAttrs();