diff --git a/OsmAnd/res/drawable-hdpi/map_ruler_center.png b/OsmAnd/res/drawable-hdpi/map_ruler_center.png new file mode 100644 index 0000000000..2f608322dc Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/map_ruler_center.png differ diff --git a/OsmAnd/res/drawable-mdpi/map_ruler_center.png b/OsmAnd/res/drawable-mdpi/map_ruler_center.png new file mode 100644 index 0000000000..177d381d75 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/map_ruler_center.png differ diff --git a/OsmAnd/res/drawable-xhdpi/map_ruler_center.png b/OsmAnd/res/drawable-xhdpi/map_ruler_center.png new file mode 100644 index 0000000000..aa54d659ad Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/map_ruler_center.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/map_ruler_center.png b/OsmAnd/res/drawable-xxhdpi/map_ruler_center.png new file mode 100644 index 0000000000..4c934217c8 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/map_ruler_center.png differ diff --git a/OsmAnd/src/net/osmand/plus/OsmandSettings.java b/OsmAnd/src/net/osmand/plus/OsmandSettings.java index b108005448..5f36717a0a 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandSettings.java +++ b/OsmAnd/src/net/osmand/plus/OsmandSettings.java @@ -703,6 +703,8 @@ public class OsmandSettings { return p; } + public final CommonPreference RULER_MODE = new EnumIntPreference<>("ruler_mode", RulerMode.FIRST, RulerMode.values()).makeGlobal(); + public final CommonPreference USE_FAST_RECALCULATION = new BooleanPreference("use_fast_recalculation", true).makeGlobal().cache(); public final CommonPreference FORCE_PRIVATE_ACCESS_ROUTING_ASKED = new BooleanPreference("force_private_access_routing", false).makeProfile().cache(); @@ -3027,4 +3029,8 @@ public class OsmandSettings { } } + public enum RulerMode { + FIRST, + SECOND + } } diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivityLayers.java b/OsmAnd/src/net/osmand/plus/activities/MapActivityLayers.java index 287c3a7ef1..2cad2d725d 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivityLayers.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivityLayers.java @@ -51,6 +51,7 @@ import net.osmand.plus.views.POIMapLayer; import net.osmand.plus.views.PointLocationLayer; import net.osmand.plus.views.PointNavigationLayer; import net.osmand.plus.views.RouteLayer; +import net.osmand.plus.views.RulerControlLayer; import net.osmand.plus.views.TransportStopsLayer; import net.osmand.plus.views.mapwidgets.MapWidgetRegistry; @@ -75,6 +76,7 @@ public class MapActivityLayers { private FavouritesLayer mFavouritesLayer; private TransportStopsLayer transportStopsLayer; private PointLocationLayer locationLayer; + private RulerControlLayer rulerControlLayer; private PointNavigationLayer navigationLayer; private MapMarkersLayer mapMarkersLayer; private ImpassableRoadsLayer impassableRoadsLayer; @@ -162,6 +164,9 @@ public class MapActivityLayers { // 7.5 Impassible roads impassableRoadsLayer = new ImpassableRoadsLayer(activity); mapView.addLayer(impassableRoadsLayer, 7.5f); + // 7.8 ruler control layer + rulerControlLayer = new RulerControlLayer(activity); + mapView.addLayer(rulerControlLayer, 7.8f); // 8. context menu layer // 9. map info layer mapInfoLayer = new MapInfoLayer(activity, routeLayer); @@ -586,6 +591,10 @@ public class MapActivityLayers { return locationLayer; } + public RulerControlLayer getRulerControlLayer() { + return rulerControlLayer; + } + public MapInfoLayer getMapInfoLayer() { return mapInfoLayer; } diff --git a/OsmAnd/src/net/osmand/plus/views/MapInfoLayer.java b/OsmAnd/src/net/osmand/plus/views/MapInfoLayer.java index 8fdd297411..df7b733ce4 100644 --- a/OsmAnd/src/net/osmand/plus/views/MapInfoLayer.java +++ b/OsmAnd/src/net/osmand/plus/views/MapInfoLayer.java @@ -193,6 +193,8 @@ public class MapInfoLayer extends OsmandMapLayer { registerSideWidget(plainTime, R.drawable.ic_action_time, R.string.map_widget_plain_time, "plain_time", false, 41); 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, R.string.map_widget_show_ruler, "ruler", false, 43); } public void recreateControls() { diff --git a/OsmAnd/src/net/osmand/plus/views/RulerControlLayer.java b/OsmAnd/src/net/osmand/plus/views/RulerControlLayer.java new file mode 100644 index 0000000000..86f73523e2 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/views/RulerControlLayer.java @@ -0,0 +1,175 @@ +package net.osmand.plus.views; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.DashPathEffect; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.Rect; +import android.text.TextPaint; + +import net.osmand.Location; +import net.osmand.data.QuadPoint; +import net.osmand.data.RotatedTileBox; +import net.osmand.plus.OsmAndFormatter; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandSettings.RulerMode; +import net.osmand.plus.R; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.helpers.AndroidUiHelper; + +public class RulerControlLayer extends OsmandMapLayer { + + private static final int TEXT_SIZE = 14; + private final MapActivity mapActivity; + private OsmandApplication app; + private boolean portrait; + private int maxRadius; + private int radius; + private int cacheZoom; + private double cacheTileX; + private double cacheTileY; + private String[] cacheDistances; + private Bitmap centerIcon; + private Paint bitmapPaint; + private Paint distancePaint; + private Paint circlePaint; + private Paint shadowPaint; + private TextPaint textPaint; + + public RulerControlLayer(MapActivity mapActivity) { + this.mapActivity = mapActivity; + } + + @Override + public void initLayer(OsmandMapTileView view) { + app = mapActivity.getMyApplication(); + portrait = AndroidUiHelper.isOrientationPortrait(mapActivity); + cacheDistances = new String[3]; + maxRadius = mapActivity.getResources().getDimensionPixelSize(R.dimen.map_ruler_width); + + centerIcon = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_ruler_center); + + bitmapPaint = new Paint(); + bitmapPaint.setAntiAlias(true); + bitmapPaint.setDither(true); + bitmapPaint.setFilterBitmap(true); + + distancePaint = new Paint(); + distancePaint.setAntiAlias(true); + distancePaint.setStyle(Style.STROKE); + distancePaint.setStrokeWidth(10); + distancePaint.setPathEffect(new DashPathEffect(new float[]{10, 10}, 0)); + + circlePaint = new Paint(); + circlePaint.setAntiAlias(true); + circlePaint.setStyle(Style.STROKE); + circlePaint.setStrokeWidth(2); + + shadowPaint = new Paint(); + shadowPaint.setAntiAlias(true); + shadowPaint.setStyle(Style.STROKE); + shadowPaint.setStrokeWidth(6); + shadowPaint.setTextSize(TEXT_SIZE * mapActivity.getResources().getDisplayMetrics().density); + shadowPaint.setColor(Color.WHITE); + + textPaint = new TextPaint(); + textPaint.setAntiAlias(true); + textPaint.setTextSize(TEXT_SIZE * mapActivity.getResources().getDisplayMetrics().density); + } + + @Override + public void onDraw(Canvas canvas, RotatedTileBox tb, DrawSettings settings) { + if (mapActivity.getMapLayers().getMapWidgetRegistry().isVisible("ruler")) { + final QuadPoint center = tb.getCenterPixelPoint(); + final RulerMode mode = app.getSettings().RULER_MODE.get(); + + drawCenterIcon(canvas, tb, center); + if (mode == RulerMode.FIRST) { + Location currentLoc = app.getLocationProvider().getLastKnownLocation(); + if (currentLoc != null) { + drawDistance(canvas, tb, center, currentLoc); + } + } else if (mode == RulerMode.SECOND) { + for (int i = 1; i < 4; i++) { + drawCircle(canvas, tb, i, center); + } + } + } + } + + private void drawCenterIcon(Canvas canvas, RotatedTileBox tb, QuadPoint center) { + canvas.rotate(-tb.getRotate(), center.x, center.y); + canvas.drawBitmap(centerIcon, center.x - centerIcon.getWidth() / 2, + center.y - centerIcon.getHeight() / 2, bitmapPaint); + canvas.rotate(tb.getRotate(), center.x, center.y); + } + + private void drawDistance(Canvas canvas, RotatedTileBox tb, QuadPoint center, Location currentLoc) { + int currentLocX = tb.getPixXFromLonNoRot(currentLoc.getLongitude()); + int currentLocY = tb.getPixYFromLatNoRot(currentLoc.getLatitude()); + canvas.drawLine(currentLocX, currentLocY, center.x, center.y, distancePaint); + } + + private void drawCircle(Canvas canvas, RotatedTileBox tb, int circleNumber, QuadPoint center) { + updateData(tb); + + Rect bounds = new Rect(); + String text = cacheDistances[circleNumber - 1]; + textPaint.getTextBounds(text, 0, text.length(), bounds); + + float left; + float bottom; + + if (portrait) { + left = center.x - bounds.width() / 2; + bottom = center.y - radius * circleNumber + bounds.height() / 2; + } else { + left = center.x + radius * circleNumber - bounds.width() / 2; + bottom = center.y + bounds.height() / 2; + } + + if (!mapActivity.getMapView().isZooming()) { + canvas.rotate(-tb.getRotate(), center.x, center.y); + canvas.drawCircle(center.x, center.y, radius * circleNumber, shadowPaint); + canvas.drawCircle(center.x, center.y, radius * circleNumber, circlePaint); + canvas.drawText(text, left, bottom, shadowPaint); + canvas.drawText(text, left, bottom, textPaint); + canvas.rotate(tb.getRotate(), center.x, center.y); + } + } + + private void updateData(RotatedTileBox tb) { + boolean move = tb.getZoom() != cacheZoom || Math.abs(tb.getCenterTileX() - cacheTileX) > 1 || + Math.abs(tb.getCenterTileY() - cacheTileY) > 1; + + if (!mapActivity.getMapView().isZooming() && move && tb.getPixWidth() > 0 && maxRadius > 0) { + cacheZoom = tb.getZoom(); + cacheTileX = tb.getCenterTileX(); + cacheTileY = tb.getCenterTileY(); + + final double dist = tb.getDistance(0, tb.getPixHeight() / 2, tb.getPixWidth(), tb.getPixHeight() / 2); + double pixDensity = tb.getPixWidth() / dist; + double roundedDist = + OsmAndFormatter.calculateRoundedDist(maxRadius / pixDensity, app); + radius = (int) (pixDensity * roundedDist); + + for (int i = 0; i < 3; i++) { + cacheDistances[i] = OsmAndFormatter.getFormattedDistance((float) roundedDist * (i + 1), + app, false).replaceAll(" ", ""); + } + } + } + + @Override + public void destroyLayer() { + + } + + @Override + public boolean drawInScreenPixels() { + return false; + } +} diff --git a/OsmAnd/src/net/osmand/plus/views/mapwidgets/MapInfoWidgetsFactory.java b/OsmAnd/src/net/osmand/plus/views/mapwidgets/MapInfoWidgetsFactory.java index 6856dc9531..d6bd62b302 100644 --- a/OsmAnd/src/net/osmand/plus/views/mapwidgets/MapInfoWidgetsFactory.java +++ b/OsmAnd/src/net/osmand/plus/views/mapwidgets/MapInfoWidgetsFactory.java @@ -1,6 +1,5 @@ package net.osmand.plus.views.mapwidgets; -import android.graphics.Color; import android.graphics.drawable.Drawable; import android.view.View; import android.view.View.OnClickListener; @@ -11,12 +10,14 @@ import android.widget.TextView; import net.osmand.Location; import net.osmand.binary.RouteDataObject; +import net.osmand.data.LatLon; import net.osmand.plus.CurrentPositionHelper; import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmAndLocationProvider; import net.osmand.plus.OsmAndLocationProvider.GPSInfo; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandSettings; +import net.osmand.plus.OsmandSettings.RulerMode; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.actions.StartGPSStatus; @@ -32,6 +33,7 @@ import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.plus.views.mapwidgets.NextTurnInfoWidget.TurnDrawable; import net.osmand.router.TurnType; import net.osmand.util.Algorithms; +import net.osmand.util.MapUtils; import java.util.Iterator; import java.util.LinkedList; @@ -108,6 +110,44 @@ public class MapInfoWidgetsFactory { return gpsInfoControl; } + public TextInfoWidget createRulerControl(final MapActivity map) { + final String title = map.getResources().getString(R.string.map_widget_show_ruler); + final TextInfoWidget rulerControl = new TextInfoWidget(map) { + @Override + public boolean updateInfo(DrawSettings drawSettings) { + if (map.getMyApplication().getSettings().RULER_MODE.get() == RulerMode.FIRST) { + Location currentLoc = map.getMyApplication().getLocationProvider().getLastKnownLocation(); + LatLon centerLoc = map.getMapLocation(); + if (currentLoc != null && centerLoc != null) { + float dist = (float) MapUtils.getDistance(currentLoc.getLatitude(), currentLoc.getLongitude(), + centerLoc.getLatitude(), centerLoc.getLongitude()); + String distance = OsmAndFormatter.getFormattedDistance(dist, map.getMyApplication()); + int ls = distance.lastIndexOf(' '); + setText(distance.substring(0, ls), distance.substring(ls + 1)); + } else { + setText(title, null); + } + } + return true; + } + }; + + rulerControl.setText(title, null); + rulerControl.setIcons(R.drawable.widget_distance_day, R.drawable.widget_distance_night); + rulerControl.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + rulerControl.setText(title, null); + final RulerMode mode = map.getMyApplication().getSettings().RULER_MODE.get(); + map.getMyApplication().getSettings().RULER_MODE + .set(mode == RulerMode.FIRST ? RulerMode.SECOND : RulerMode.FIRST); + map.refreshMap(); + } + }); + + return rulerControl; + } + public static class TopToolbarController { private TopToolbarControllerType type; diff --git a/OsmAnd/src/net/osmand/plus/views/mapwidgets/RouteInfoWidgetsFactory.java b/OsmAnd/src/net/osmand/plus/views/mapwidgets/RouteInfoWidgetsFactory.java index 1e420f964f..f1e54c416c 100644 --- a/OsmAnd/src/net/osmand/plus/views/mapwidgets/RouteInfoWidgetsFactory.java +++ b/OsmAnd/src/net/osmand/plus/views/mapwidgets/RouteInfoWidgetsFactory.java @@ -6,11 +6,13 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.BlurMaskFilter; import android.graphics.BlurMaskFilter.Blur; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; +import android.graphics.DashPathEffect; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint.Style; @@ -29,10 +31,12 @@ import android.widget.TextView; import net.osmand.Location; import net.osmand.binary.RouteDataObject; import net.osmand.data.LatLon; +import net.osmand.data.QuadPoint; import net.osmand.data.RotatedTileBox; import net.osmand.plus.MapMarkersHelper.MapMarker; import net.osmand.plus.OsmAndFormatter; import net.osmand.plus.OsmAndLocationProvider; +import net.osmand.plus.OsmAndLocationProvider.OsmAndLocationListener; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandSettings; import net.osmand.plus.OsmandSettings.OsmandPreference; @@ -50,6 +54,7 @@ import net.osmand.plus.routing.RouteCalculationResult.NextDirectionInfo; import net.osmand.plus.routing.RouteDirectionInfo; import net.osmand.plus.routing.RoutingHelper; import net.osmand.plus.views.AnimateDraggingMapThread; +import net.osmand.plus.views.OsmandMapLayer; import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.plus.views.TurnPathHelper; @@ -375,7 +380,6 @@ public class RouteInfoWidgetsFactory { return batteryControl; } - public TextInfoWidget createMaxSpeedControl(final MapActivity map) { final RoutingHelper rh = map.getMyApplication().getRoutingHelper(); final OsmAndLocationProvider locationProvider = map.getMyApplication().getLocationProvider();