Merge pull request #6980 from osmandapp/CompassRulerControl

Add compass ruler
This commit is contained in:
Alexey 2019-05-26 14:07:55 +03:00 committed by GitHub
commit e15697a46c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 312 additions and 30 deletions

View file

@ -258,6 +258,8 @@
<color name="shadow_color">#33888888</color>
<color name="compass_control_active">#EE5622</color>
<color name="sherpafy_selection">#ff33b5e5</color>
<color name="sherpafy_add_text">#b9b9b9</color>

View file

@ -8,5 +8,8 @@
<!-- Time control widget ids-->
<item name="time_control_widget_state_arrival_time" type="id"/>
<item name="time_control_widget_state_time_to_go" type="id"/>
<!-- Compass control widget ids-->
<item name="compass_ruler_control_widget_state_show" type="id"/>
<item name="compass_ruler_control_widget_state_hide" type="id"/>
</resources>

View file

@ -12,6 +12,8 @@
-->
<string name="show_compass_ruler">Show compass ruler</string>
<string name="hide_compass_ruler">Hide compass ruler</string>
<string name="select_icon_profile_dialog_title">Select icon</string>
<string name="settings_routing_mode_string">Mode: %s</string>
<string name="settings_derived_routing_mode_string">User Mode, derived from: %s</string>

View file

@ -803,6 +803,8 @@ public class OsmandSettings {
public final CommonPreference<RulerMode> RULER_MODE = new EnumIntPreference<>("ruler_mode", RulerMode.FIRST, RulerMode.values()).makeGlobal();
public final OsmandPreference<Boolean> SHOW_COMPASS_CONTROL_RULER = new BooleanPreference("show_compass_ruler", true).makeGlobal();
public final CommonPreference<Boolean> SHOW_LINES_TO_FIRST_MARKERS = new BooleanPreference("show_lines_to_first_markers", false).makeProfile();
public final CommonPreference<Boolean> SHOW_ARROWS_TO_FIRST_MARKERS = new BooleanPreference("show_arrows_to_first_markers", false).makeProfile();

View file

@ -18,6 +18,7 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.helpers.AndroidUiHelper;
import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu.TrackChartPoints;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.CompassRulerControlWidgetState;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopCoordinatesView;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopTextView;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;
@ -200,7 +201,7 @@ public class MapInfoLayer extends OsmandMapLayer {
TextInfoWidget battery = ric.createBatteryControl(map);
registerSideWidget(battery, R.drawable.ic_action_battery, R.string.map_widget_battery, "battery", false, 42);
TextInfoWidget ruler = mic.createRulerControl(map);
registerSideWidget(ruler, R.drawable.ic_action_ruler_circle, R.string.map_widget_ruler_control, "ruler", false, 43);
registerSideWidget(ruler, new CompassRulerControlWidgetState(app), "ruler", false, 43);
}
public void recreateControls() {

View file

@ -3,17 +3,23 @@ package net.osmand.plus.views;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Handler;
import android.os.Message;
import android.support.v4.content.ContextCompat;
import android.view.MotionEvent;
import android.view.View;
import net.osmand.AndroidUtils;
import net.osmand.Location;
import net.osmand.data.LatLon;
import net.osmand.data.QuadPoint;
@ -36,6 +42,7 @@ public class RulerControlLayer extends OsmandMapLayer {
private static final long DELAY_BEFORE_DRAW = 500;
private static final int TEXT_SIZE = 14;
private static final int DISTANCE_TEXT_SIZE = 16;
private static final int COMPASS_CIRCLE_ID = 2;
private final MapActivity mapActivity;
private OsmandApplication app;
@ -74,11 +81,26 @@ public class RulerControlLayer extends OsmandMapLayer {
private Bitmap centerIconDay;
private Bitmap centerIconNight;
private Paint bitmapPaint;
private Paint triangleHeadingPaint;
private Paint triangleNorthPaint;
private Paint redLinesPaint;
private Paint blueLinesPaint;
private RenderingLineAttributes lineAttrs;
private RenderingLineAttributes lineFontAttrs;
private RenderingLineAttributes circleAttrs;
private RenderingLineAttributes circleAttrsAlt;
private Path compass = new Path();
private Path arrow = new Path();
private Path arrowArc = new Path();
private Path redCompassLines = new Path();
private double[] degrees = new double[72];
private int[] arcColors = {Color.parseColor("#00237BFF"), Color.parseColor("#237BFF"), Color.parseColor("#00237BFF")};
private float cachedHeading = 0;
private Handler handler;
public RulerControlLayer(MapActivity mapActivity) {
@ -118,6 +140,14 @@ public class RulerControlLayer extends OsmandMapLayer {
bitmapPaint.setDither(true);
bitmapPaint.setFilterBitmap(true);
int colorNorthArrow = ContextCompat.getColor(app, R.color.compass_control_active);
int colorHeadingArrow = ContextCompat.getColor(app, R.color.active_buttons_and_links_light);
triangleNorthPaint = initPaintWithStyle(Style.FILL, colorNorthArrow);
triangleHeadingPaint = initPaintWithStyle(Style.FILL, colorHeadingArrow);
redLinesPaint = initPaintWithStyle(Style.STROKE, colorNorthArrow);
blueLinesPaint = initPaintWithStyle(Style.STROKE, colorHeadingArrow);
lineAttrs = new RenderingLineAttributes("rulerLine");
float circleTextSize = TEXT_SIZE * mapActivity.getResources().getDisplayMetrics().density;
@ -141,6 +171,18 @@ public class RulerControlLayer extends OsmandMapLayer {
view.refreshMap();
}
};
for (int i = 0; i < 72; i++) {
degrees[i] = Math.toRadians(i * 5);
}
}
private Paint initPaintWithStyle(Paint.Style style, int color) {
Paint paint = new Paint();
paint.setStyle(style);
paint.setColor(color);
paint.setAntiAlias(true);
return paint;
}
@Override
@ -190,6 +232,7 @@ public class RulerControlLayer extends OsmandMapLayer {
circleAttrsAlt.paint2.setStyle(Style.FILL);
final QuadPoint center = tb.getCenterPixelPoint();
final RulerMode mode = app.getSettings().RULER_MODE.get();
boolean showCompass = app.getSettings().SHOW_COMPASS_CONTROL_RULER.get();
final long currentTime = System.currentTimeMillis();
if (cacheMultiTouchEndTime != view.getMultiTouchEndTime()) {
@ -226,9 +269,17 @@ public class RulerControlLayer extends OsmandMapLayer {
}
if (mode == RulerMode.FIRST || mode == RulerMode.SECOND) {
updateData(tb, center);
if (showCompass) {
updateHeading();
resetDrawingPaths();
}
RenderingLineAttributes attrs = mode == RulerMode.FIRST ? circleAttrs : circleAttrsAlt;
for (int i = 1; i <= cacheDistances.size(); i++) {
drawCircle(canvas, tb, i, center, attrs);
if (showCompass && i == COMPASS_CIRCLE_ID) {
drawCompassCircle(canvas, tb, center, attrs);
} else {
drawCircle(canvas, tb, i, center, attrs);
}
}
}
}
@ -239,6 +290,20 @@ public class RulerControlLayer extends OsmandMapLayer {
rightWidgetsPanel.getVisibility() == View.VISIBLE;
}
private void updateHeading() {
Float heading = mapActivity.getMapViewTrackingUtilities().getHeading();
if (heading != null && heading != cachedHeading) {
cachedHeading = heading;
}
}
private void resetDrawingPaths() {
redCompassLines.reset();
arrowArc.reset();
compass.reset();
arrow.reset();
}
private void refreshMapDelayed() {
handler.sendEmptyMessageDelayed(0, DRAW_TIME + 50);
}
@ -293,7 +358,7 @@ public class RulerControlLayer extends OsmandMapLayer {
}
private void drawCenterIcon(Canvas canvas, RotatedTileBox tb, QuadPoint center, boolean nightMode,
RulerMode mode) {
RulerMode mode) {
canvas.rotate(-tb.getRotate(), center.x, center.y);
if (nightMode || mode == RulerMode.SECOND) {
canvas.drawBitmap(centerIconNight, center.x - centerIconNight.getWidth() / 2,
@ -391,42 +456,200 @@ public class RulerControlLayer extends OsmandMapLayer {
}
private void drawCircle(Canvas canvas, RotatedTileBox tb, int circleNumber, QuadPoint center,
RenderingLineAttributes attrs) {
RenderingLineAttributes attrs) {
if (!tb.isZoomAnimated()) {
Rect bounds = new Rect();
float circleRadius = radius * circleNumber;
String text = cacheDistances.get(circleNumber - 1);
attrs.paint2.getTextBounds(text, 0, text.length(), bounds);
// coords of left or top text
float x1 = 0;
float y1 = 0;
// coords of right or bottom text
float x2 = 0;
float y2 = 0;
if (textSide == TextSide.VERTICAL) {
x1 = center.x - bounds.width() / 2;
y1 = center.y - radius * circleNumber + bounds.height() / 2;
x2 = center.x - bounds.width() / 2;
y2 = center.y + radius * circleNumber + bounds.height() / 2;
} else if (textSide == TextSide.HORIZONTAL) {
x1 = center.x - radius * circleNumber - bounds.width() / 2;
y1 = center.y + bounds.height() / 2;
x2 = center.x + radius * circleNumber - bounds.width() / 2;
y2 = center.y + bounds.height() / 2;
}
float[] textCoords = calculateDistanceTextCoords(text, circleRadius, center, attrs);
canvas.rotate(-tb.getRotate(), center.x, center.y);
canvas.drawCircle(center.x, center.y, radius * circleNumber, attrs.shadowPaint);
canvas.drawCircle(center.x, center.y, radius * circleNumber, attrs.paint);
canvas.drawText(text, x1, y1, attrs.paint3);
canvas.drawText(text, x1, y1, attrs.paint2);
canvas.drawText(text, x2, y2, attrs.paint3);
canvas.drawText(text, x2, y2, attrs.paint2);
drawTextCoords(canvas, text, textCoords, attrs);
canvas.rotate(tb.getRotate(), center.x, center.y);
}
}
private void drawTextCoords(Canvas canvas, String text, float[] textCoords, RenderingLineAttributes attrs) {
canvas.drawText(text, textCoords[0], textCoords[1], attrs.paint3);
canvas.drawText(text, textCoords[0], textCoords[1], attrs.paint2);
canvas.drawText(text, textCoords[2], textCoords[3], attrs.paint3);
canvas.drawText(text, textCoords[2], textCoords[3], attrs.paint2);
}
private float[] calculateDistanceTextCoords(String text, float drawingTextRadius, QuadPoint center, RenderingLineAttributes attrs) {
Rect bounds = new Rect();
attrs.paint2.getTextBounds(text, 0, text.length(), bounds);
// coords of left or top text
float x1 = 0;
float y1 = 0;
// coords of right or bottom text
float x2 = 0;
float y2 = 0;
if (textSide == TextSide.VERTICAL) {
x1 = center.x - bounds.width() / 2;
y1 = center.y - drawingTextRadius + bounds.height() / 2;
x2 = center.x - bounds.width() / 2;
y2 = center.y + drawingTextRadius + bounds.height() / 2;
} else if (textSide == TextSide.HORIZONTAL) {
x1 = center.x - drawingTextRadius - bounds.width() / 2;
y1 = center.y + bounds.height() / 2;
x2 = center.x + drawingTextRadius - bounds.width() / 2;
y2 = center.y + bounds.height() / 2;
}
return new float[]{x1, y1, x2, y2};
}
private void drawCompassCircle(Canvas canvas, RotatedTileBox tileBox, QuadPoint center,
RenderingLineAttributes attrs) {
if (!tileBox.isZoomAnimated()) {
float radiusLength = radius * COMPASS_CIRCLE_ID;
float innerRadiusLength = radiusLength - attrs.paint.getStrokeWidth() / 2;
updateArcShader(radiusLength, center);
updateCompassPaths(center, innerRadiusLength, radiusLength);
drawCardinalDirections(canvas, center, radiusLength, tileBox, attrs);
redLinesPaint.setStrokeWidth(attrs.paint.getStrokeWidth());
blueLinesPaint.setStrokeWidth(attrs.paint.getStrokeWidth());
canvas.drawPath(compass, attrs.shadowPaint);
canvas.drawPath(compass, attrs.paint);
canvas.drawPath(redCompassLines, redLinesPaint);
canvas.rotate(cachedHeading, center.x, center.y);
canvas.drawPath(arrowArc, blueLinesPaint);
canvas.rotate(-cachedHeading, center.x, center.y);
canvas.drawPath(arrow, attrs.shadowPaint);
canvas.drawPath(arrow, triangleNorthPaint);
canvas.rotate(cachedHeading, center.x, center.y);
canvas.drawPath(arrow, attrs.shadowPaint);
canvas.drawPath(arrow, triangleHeadingPaint);
canvas.rotate(-cachedHeading, center.x, center.y);
String text = cacheDistances.get(COMPASS_CIRCLE_ID - 1);
float[] textCoords = calculateDistanceTextCoords(text, radiusLength + AndroidUtils.dpToPx(app, 16), center, attrs);
canvas.rotate(-tileBox.getRotate(), center.x, center.y);
drawTextCoords(canvas, text, textCoords, attrs);
canvas.rotate(tileBox.getRotate(), center.x, center.y);
}
}
private void updateCompassPaths(QuadPoint center, float innerRadiusLength, float radiusLength) {
compass.addCircle(center.x, center.y, radiusLength, Path.Direction.CCW);
arrowArc.addArc(new RectF(center.x - radiusLength, center.y - radiusLength, center.x + radiusLength, center.y + radiusLength), -45, -90);
for (int i = 0; i < degrees.length; i++) {
double degree = degrees[i];
float x = (float) Math.cos(degree);
float y = -(float) Math.sin(degree);
float lineStartX = center.x + x * innerRadiusLength;
float lineStartY = center.y + y * innerRadiusLength;
float lineLength = getCompassLineHeight(i);
float lineStopX = center.x + x * (innerRadiusLength - lineLength);
float lineStopY = center.y + y * (innerRadiusLength - lineLength);
if (i == 18) {
float shortLineMargin = AndroidUtils.dpToPx(app, 5.66f);
float shortLineHeight = AndroidUtils.dpToPx(app, 2.94f);
float startY = center.y + y * (radiusLength - shortLineMargin);
float stopY = center.y + y * (radiusLength - shortLineMargin - shortLineHeight);
compass.moveTo(center.x, startY);
compass.lineTo(center.x, stopY);
float firstPointY = center.y + y * (radiusLength + AndroidUtils.dpToPx(app, 5));
float secondPointX = center.x - AndroidUtils.dpToPx(app, 4);
float secondPointY = center.y + y * (radiusLength - AndroidUtils.dpToPx(app, 2));
float thirdPointX = center.x + AndroidUtils.dpToPx(app, 4);
float thirdPointY = center.y + y * (radiusLength - AndroidUtils.dpToPx(app, 2));
arrow.moveTo(center.x, firstPointY);
arrow.lineTo(secondPointX, secondPointY);
arrow.lineTo(thirdPointX, thirdPointY);
arrow.lineTo(center.x, firstPointY);
arrow.close();
} else {
compass.moveTo(lineStartX, lineStartY);
compass.lineTo(lineStopX, lineStopY);
}
if (i % 9 == 0 && i % 6 != 0) {
redCompassLines.moveTo(lineStartX, lineStartY);
redCompassLines.lineTo(lineStopX, lineStopY);
}
}
}
private float getCompassLineHeight(int index) {
if (index % 6 == 0) {
return AndroidUtils.dpToPx(app, 8);
} else if (index % 9 == 0 || index % 2 != 0) {
return AndroidUtils.dpToPx(app, 3);
} else {
return AndroidUtils.dpToPx(app, 6);
}
}
private void drawCardinalDirections(Canvas canvas, QuadPoint center, float radiusLength, RotatedTileBox tileBox, RenderingLineAttributes attrs) {
float textMargin = AndroidUtils.dpToPx(app, 14);
attrs.paint2.setTextAlign(Paint.Align.CENTER);
attrs.paint3.setTextAlign(Paint.Align.CENTER);
for (int i = 0; i < degrees.length; i += 9) {
String cardinalDirection = getCardinalDirection(i);
if (cardinalDirection != null) {
float textWidth = AndroidUtils.getTextWidth(attrs.paint2.getTextSize(), cardinalDirection);
canvas.save();
canvas.translate(center.x, center.y);
canvas.rotate(i * 5 + 90);
canvas.translate(0, radiusLength - textMargin - textWidth / 2);
canvas.rotate(-i * 5 - tileBox.getRotate() - 90);
canvas.drawText(cardinalDirection, 0, 0, attrs.paint3);
canvas.drawText(cardinalDirection, 0, 0, attrs.paint2);
canvas.restore();
}
}
}
private void updateArcShader(float radiusLength, QuadPoint center) {
float arcLength = (float) (2 * Math.PI * radiusLength * (90f / 360));
LinearGradient shader = new LinearGradient((float) (center.x - arcLength * 0.25), center.y, (float) (center.x + arcLength * 0.25), center.y, arcColors, null, Shader.TileMode.CLAMP);
blueLinesPaint.setShader(shader);
}
private String getCardinalDirection(int i) {
if (i == 0) {
return "E";
} else if (i == 9) {
return "NE";
} else if (i == 18) {
return "N";
} else if (i == 27) {
return "NW";
} else if (i == 36) {
return "W";
} else if (i == 45) {
return "SW";
} else if (i == 54) {
return "S";
} else if (i == 63) {
return "SE";
}
return null;
}
private enum TextSide {
VERTICAL,
HORIZONTAL
@ -441,4 +664,4 @@ public class RulerControlLayer extends OsmandMapLayer {
public boolean drawInScreenPixels() {
return false;
}
}
}

View file

@ -56,6 +56,7 @@ import net.osmand.plus.routing.RoutingHelper;
import net.osmand.plus.views.OsmandMapLayer.DrawSettings;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.plus.views.RulerControlLayer;
import net.osmand.plus.views.mapwidgets.MapWidgetRegistry.WidgetState;
import net.osmand.plus.views.mapwidgets.NextTurnInfoWidget.TurnDrawable;
import net.osmand.router.TurnType;
import net.osmand.util.Algorithms;
@ -138,6 +139,54 @@ public class MapInfoWidgetsFactory {
return gpsInfoControl;
}
public static class CompassRulerControlWidgetState extends WidgetState {
public static final int COMPASS_CONTROL_WIDGET_STATE_SHOW = R.id.compass_ruler_control_widget_state_show;
public static final int COMPASS_CONTROL_WIDGET_STATE_HIDE = R.id.compass_ruler_control_widget_state_hide;
private final OsmandSettings.OsmandPreference<Boolean> showCompass;
public CompassRulerControlWidgetState(OsmandApplication ctx) {
super(ctx);
showCompass = ctx.getSettings().SHOW_COMPASS_CONTROL_RULER;
}
@Override
public int getMenuTitleId() {
return R.string.map_widget_ruler_control;
}
@Override
public int getMenuIconId() {
return R.drawable.ic_action_ruler_circle;
}
@Override
public int getMenuItemId() {
return showCompass.get() ? COMPASS_CONTROL_WIDGET_STATE_SHOW : COMPASS_CONTROL_WIDGET_STATE_HIDE;
}
@Override
public int[] getMenuTitleIds() {
return new int[]{R.string.show_compass_ruler, R.string.hide_compass_ruler};
}
@Override
public int[] getMenuIconIds() {
return new int[]{R.drawable.ic_action_compass_widget, R.drawable.ic_action_compass_widget_hide};
}
@Override
public int[] getMenuItemIds() {
return new int[]{COMPASS_CONTROL_WIDGET_STATE_SHOW, COMPASS_CONTROL_WIDGET_STATE_HIDE};
}
@Override
public void changeState(int stateId) {
showCompass.set(stateId == COMPASS_CONTROL_WIDGET_STATE_SHOW);
}
}
public TextInfoWidget createRulerControl(final MapActivity map) {
final String title = "";
final TextInfoWidget rulerControl = new TextInfoWidget(map) {