From 3f9b0f03d436472112ebcd38cea01e47deb06ced Mon Sep 17 00:00:00 2001 From: simon Date: Mon, 31 Aug 2020 11:35:00 +0300 Subject: [PATCH] display loaded tile --- .../plus/activities/ServerActivity.java | 6 +- .../src/net/osmand/plus/server/ApiRouter.java | 96 +- .../osmand/plus/server/map/LayersDraw.java | 60 +- .../plus/server/map/MapMarkersMiniLayer.java | 646 ++++++++ .../map/MapMarkersWidgetsMiniFactory.java | 306 ++++ .../plus/server/map/MapTextMiniLayer.java | 223 +++ .../plus/server/map/MapTileMiniLayer.java | 276 ++++ .../plus/server/map/MapVectorMiniLayer.java | 232 +++ .../osmand/plus/server/map/MyCustomLayer.java | 35 + .../plus/server/map/OsmandMapMiniLayer.java | 482 ++++++ .../server/map/OsmandMapTileMiniView.java | 1314 +++++++++++++++++ .../plus/server/map/POIMapLayerMini.java | 471 ++++++ 12 files changed, 4108 insertions(+), 39 deletions(-) create mode 100644 OsmAnd/src/net/osmand/plus/server/map/MapMarkersMiniLayer.java create mode 100644 OsmAnd/src/net/osmand/plus/server/map/MapMarkersWidgetsMiniFactory.java create mode 100644 OsmAnd/src/net/osmand/plus/server/map/MapTextMiniLayer.java create mode 100644 OsmAnd/src/net/osmand/plus/server/map/MapTileMiniLayer.java create mode 100644 OsmAnd/src/net/osmand/plus/server/map/MapVectorMiniLayer.java create mode 100644 OsmAnd/src/net/osmand/plus/server/map/MyCustomLayer.java create mode 100644 OsmAnd/src/net/osmand/plus/server/map/OsmandMapMiniLayer.java create mode 100644 OsmAnd/src/net/osmand/plus/server/map/OsmandMapTileMiniView.java create mode 100644 OsmAnd/src/net/osmand/plus/server/map/POIMapLayerMini.java diff --git a/OsmAnd/src/net/osmand/plus/activities/ServerActivity.java b/OsmAnd/src/net/osmand/plus/activities/ServerActivity.java index 9e453b5f8e..eda412c599 100644 --- a/OsmAnd/src/net/osmand/plus/activities/ServerActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/ServerActivity.java @@ -91,8 +91,10 @@ public class ServerActivity extends AppCompatActivity { } private void deInitServer() { - server.closeAllConnections(); - server.stop(); + if (server != null){ + server.closeAllConnections(); + server.stop(); + } initialized = false; } diff --git a/OsmAnd/src/net/osmand/plus/server/ApiRouter.java b/OsmAnd/src/net/osmand/plus/server/ApiRouter.java index 40fba76882..bb36c3f215 100644 --- a/OsmAnd/src/net/osmand/plus/server/ApiRouter.java +++ b/OsmAnd/src/net/osmand/plus/server/ApiRouter.java @@ -2,13 +2,26 @@ package net.osmand.plus.server; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; import android.util.Log; import android.webkit.MimeTypeMap; - +import androidx.core.util.Pair; import com.google.gson.Gson; - +import fi.iki.elonen.NanoHTTPD; import net.osmand.data.FavouritePoint; +import net.osmand.data.GeometryTile; +import net.osmand.data.QuadPointDouble; +import net.osmand.data.QuadRect; +import net.osmand.map.ITileSource; +import net.osmand.map.TileSourceManager; import net.osmand.plus.OsmandApplication; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.resources.ResourceManager; +import net.osmand.plus.server.map.LayersDraw; +import net.osmand.plus.server.map.MapTileMiniLayer; +import net.osmand.plus.server.map.OsmandMapMiniLayer; +import net.osmand.plus.server.map.OsmandMapTileMiniView; import java.io.*; import java.nio.charset.Charset; @@ -16,9 +29,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import fi.iki.elonen.NanoHTTPD; -import net.osmand.plus.activities.MapActivity; - import static fi.iki.elonen.NanoHTTPD.newFixedLengthResponse; public class ApiRouter { @@ -47,22 +57,74 @@ public class ApiRouter { }; endpoints.put(favorites.uri,favorites); - ApiEndpoint tile = new ApiEndpoint(); + final ApiEndpoint tile = new ApiEndpoint(); tile.uri = "/tile"; tile.apiCall = new ApiEndpoint.ApiCall(){ @Override public NanoHTTPD.Response call(NanoHTTPD.IHTTPSession session) { - Bitmap bitmap = mapActivity.getMapView().currentCanvas; - //androidContext.getApplicationContext().get - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); - byte[] byteArray = stream.toByteArray(); - ByteArrayInputStream str = new ByteArrayInputStream(byteArray); - return newFixedLengthResponse( - NanoHTTPD.Response.Status.OK, - "image/png", - str, - str.available()); + try{ + ITileSource map = TileSourceManager.getMapillaryVectorSource(); + Bitmap bitmap = Bitmap.createBitmap(512,512,Bitmap.Config.ARGB_8888);//mapActivity.getMapView().currentCanvas; + //OsmandMapTileView tileView = new OsmandMapTileView(mapActivity,300,300); + OsmandMapTileMiniView tileView = new OsmandMapTileMiniView(androidContext,512,512); + Canvas canvas = new Canvas(bitmap); + LayersDraw.createLayers(androidContext,canvas, tileView); + Paint p = new Paint(); + p.setStyle(Paint.Style.FILL_AND_STROKE); + p.setColor(Color.BLACK); + //canvas.drawBitmap(bitmap ,0, 0, null); + boolean nightMode = androidContext.getDaynightHelper().isNightMode(); + OsmandMapMiniLayer.DrawSettings drawSettings = new OsmandMapMiniLayer.DrawSettings(nightMode, false); + tileView.refreshMapInternal(drawSettings); + tileView.refreshMap(); + MapTileMiniLayer mapTileLayer = new MapTileMiniLayer(true); + //mapView.addLayer(mapTileLayer, 0.0f); + //mapTileLayer.drawTileMap(canvas,tileView.getCurrentRotatedTileBox()); + tileView.drawOverMap(canvas,tileView.getCurrentRotatedTileBox(),drawSettings); + //androidContext.getApplicationContext().get + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + //bitmap = tileView.currentCanvas; + final QuadRect tilesRect = tileView.getCurrentRotatedTileBox().getTileBounds(); + int left = (int) Math.floor(tilesRect.left); + int top = (int) Math.floor(tilesRect.top); + int width = (int) Math.ceil(tilesRect.right - left); + int height = (int) Math.ceil(tilesRect.bottom - top); + int dzoom = 1; + int div = (int) Math.pow(2.0, dzoom); + + ResourceManager mgr = androidContext.getResourceManager(); + int tileX = (left ) / div; + int tileY = (top) / div; + String tileId = mgr.calculateTileId(map, tileX, tileY, 14); + //androidContext.getResourceManager(). + // getMapTileDownloader(). + //String imgTileId = mgr.calculateTileId(map, tileX / div, tileY / div, nzoom - kzoom); + Map> tiles = new HashMap<>(); + //bitmap = null; + boolean first = true; + //while (bitmap == null){ +// bitmap = androidContext.getResourceManager().getBitmapTilesCache().getTileForMapAsync( +// tileId,map,tileX,tileY,14,first +// ); +// first = false; + //} + canvas.drawLine(0,0,canvas.getWidth(),canvas.getHeight(), p); + //bitmap = tileView.currentCanvas; + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); + + byte[] byteArray = stream.toByteArray(); + ByteArrayInputStream str = new ByteArrayInputStream(byteArray); + + return newFixedLengthResponse( + NanoHTTPD.Response.Status.OK, + "image/png", + str, + str.available()); + } + catch (Exception e){ + e.printStackTrace(); + } + return ErrorResponses.response500; } }; endpoints.put(tile.uri,tile); diff --git a/OsmAnd/src/net/osmand/plus/server/map/LayersDraw.java b/OsmAnd/src/net/osmand/plus/server/map/LayersDraw.java index a54b40f44d..27787fb8ab 100644 --- a/OsmAnd/src/net/osmand/plus/server/map/LayersDraw.java +++ b/OsmAnd/src/net/osmand/plus/server/map/LayersDraw.java @@ -1,55 +1,67 @@ package net.osmand.plus.server.map; -import net.osmand.StateChangedListener; +import android.graphics.Canvas; +import net.osmand.data.RotatedTileBox; +import net.osmand.map.ITileSource; +import net.osmand.map.TileSourceManager; import net.osmand.plus.OsmandApplication; -import net.osmand.plus.OsmandPlugin; -import net.osmand.plus.measurementtool.MeasurementToolLayer; import net.osmand.plus.render.MapVectorLayer; import net.osmand.plus.routing.RoutingHelper; import net.osmand.plus.views.MapTileLayer; -import net.osmand.plus.views.OsmandMapTileView; -import net.osmand.plus.views.layers.*; +import net.osmand.plus.views.layers.FavouritesLayer; +import net.osmand.plus.views.layers.GPXLayer; +import net.osmand.plus.views.layers.MapTextLayer; +import net.osmand.plus.views.layers.RouteLayer; public class LayersDraw { - public static void createLayers(OsmandApplication app, final OsmandMapTileView mapView) { + public static void createLayers(OsmandApplication app, Canvas canvas, final OsmandMapTileMiniView mapView) { RoutingHelper routingHelper = app.getRoutingHelper(); // first create to make accessible MapTextLayer mapTextLayer = new MapTextLayer(); // 5.95 all labels - mapView.addLayer(mapTextLayer, 5.95f); + //mapView.addLayer(mapTextLayer, 5.95f); // 8. context menu layer - //ContextMenuLayer contextMenuLayer = new ContextMenuLayer(activity); + //ContextMenuLayer contextMenuLayer = new ContextMenuLayer(app); //mapView.addLayer(contextMenuLayer, 8); // mapView.addLayer(underlayLayer, -0.5f); - MapTileLayer mapTileLayer = new MapTileLayer(true); + RotatedTileBox currentTileBlock = mapView.getCurrentRotatedTileBox(); + MapTileMiniLayer mapTileLayer = new MapTileMiniLayer(true); mapView.addLayer(mapTileLayer, 0.0f); - mapView.setMainLayer(mapTileLayer); + ITileSource map = TileSourceManager.getMapillaryVectorSource(); + mapTileLayer.setMap(map); + mapTileLayer.drawTileMap(canvas,currentTileBlock); + //mapView.setMainLayer(mapTileLayer); // 0.5 layer - MapVectorLayer mapVectorLayer = new MapVectorLayer(mapTileLayer, false); + MapVectorMiniLayer mapVectorLayer = new MapVectorMiniLayer(mapTileLayer, false); mapView.addLayer(mapVectorLayer, 0.5f); - + mapVectorLayer.onPrepareBufferImage(canvas, + currentTileBlock, + new OsmandMapMiniLayer.DrawSettings(false)); //DownloadedRegionsLayer downloadedRegionsLayer = new DownloadedRegionsLayer(activity); //mapView.addLayer(downloadedRegionsLayer, 0.5f); // 0.9 gpx layer GPXLayer gpxLayer = new GPXLayer(); - mapView.addLayer(gpxLayer, 0.9f); + //mapView.addLayer(gpxLayer, 0.9f); // 1. route layer RouteLayer routeLayer = new RouteLayer(routingHelper); - mapView.addLayer(routeLayer, 1); + //mapView.addLayer(routeLayer, 1); // 2. osm bugs layer // 3. poi layer - //POIMapLayer poiMapLayer = new POIMapLayer(activity); - //mapView.addLayer(poiMapLayer, 3); + POIMapLayerMini poiMapLayer = new POIMapLayerMini(app); + mapView.addLayer(poiMapLayer, 3); + + poiMapLayer.onPrepareBufferImage(canvas, currentTileBlock, + new OsmandMapMiniLayer.DrawSettings(false)); // 4. favorites layer FavouritesLayer mFavouritesLayer = new FavouritesLayer(); - mapView.addLayer(mFavouritesLayer, 4); + //mapView.addLayer(mFavouritesLayer, 4); // 4.6 measurement tool layer - MeasurementToolLayer measurementToolLayer = new MeasurementToolLayer(); - mapView.addLayer(measurementToolLayer, 4.6f); + //MeasurementToolLayer measurementToolLayer = new MeasurementToolLayer(); + //mapView.addLayer(measurementToolLayer, 4.6f); // 5. transport layer //TransportStopsLayer transportStopsLayer = new TransportStopsLayer(activity); //mapView.addLayer(transportStopsLayer, 5); @@ -61,8 +73,11 @@ public class LayersDraw { //PointNavigationLayer navigationLayer = new PointNavigationLayer(activity); //mapView.addLayer(navigationLayer, 7); // 7.3 map markers layer - //MapMarkersLayer mapMarkersLayer = new MapMarkersLayer(activity); + //MapMarkersMiniLayer mapMarkersLayer = new MapMarkersMiniLayer(app); //mapView.addLayer(mapMarkersLayer, 7.3f); + //MyCustomLayer layer = new MyCustomLayer(); + //mapView.addLayer(layer,2); + // 7.5 Impassible roads //ImpassableRoadsLayer impassableRoadsLayer = new ImpassableRoadsLayer(activity); //mapView.addLayer(impassableRoadsLayer, 7.5f); @@ -92,6 +107,11 @@ public class LayersDraw { // }; // app.getSettings().MAP_TRANSPARENCY.addListener(transparencyListener); + + //OsmandPlugin.createLayers(mapView, activity); + //app.getAppCustomization().createLayers(mapView, activity); + //app.getAidlApi().registerMapLayers(activity); + //return OsmandPlugin.createLayers(mapView, app); //app.getAppCustomization().createLayers(mapView, activity); //app.getAidlApi().registerMapLayers(activity); diff --git a/OsmAnd/src/net/osmand/plus/server/map/MapMarkersMiniLayer.java b/OsmAnd/src/net/osmand/plus/server/map/MapMarkersMiniLayer.java new file mode 100644 index 0000000000..31161b5d52 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/server/map/MapMarkersMiniLayer.java @@ -0,0 +1,646 @@ +package net.osmand.plus.server.map; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PathMeasure; +import android.graphics.PointF; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Message; +import android.text.TextPaint; +import android.text.TextUtils; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import com.google.android.material.snackbar.Snackbar; + +import net.osmand.GPXUtilities.TrkSegment; +import net.osmand.Location; +import net.osmand.data.Amenity; +import net.osmand.data.LatLon; +import net.osmand.data.PointDescription; +import net.osmand.data.QuadPoint; +import net.osmand.data.RotatedTileBox; +import net.osmand.plus.MapMarkersHelper; +import net.osmand.plus.MapMarkersHelper.MapMarker; +import net.osmand.plus.OsmAndConstants; +import net.osmand.plus.OsmAndFormatter; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.R; +import net.osmand.plus.TargetPointsHelper.TargetPoint; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.base.MapViewTrackingUtilities; +import net.osmand.plus.views.OsmandMapLayer; +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.ContextMenuLayer.ApplyMovedObjectCallback; +import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider; +import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProviderSelection; +import net.osmand.plus.views.layers.geometry.GeometryWay; +import net.osmand.plus.views.mapwidgets.MapMarkersWidgetsFactory; +import net.osmand.util.MapUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class MapMarkersMiniLayer extends OsmandMapMiniLayer implements IContextMenuProvider, + IContextMenuProviderSelection, ContextMenuLayer.IMoveObjectProvider { + + private static final long USE_FINGER_LOCATION_DELAY = 1000; + private static final int MAP_REFRESH_MESSAGE = OsmAndConstants.UI_HANDLER_MAP_VIEW + 6; + protected static final int DIST_TO_SHOW = 80; + private OsmandApplication application; + + private OsmandMapTileMiniView view; + + private MapMarkersWidgetsMiniFactory widgetsFactory; + + private Paint bitmapPaint; + private Bitmap markerBitmapBlue; + private Bitmap markerBitmapGreen; + private Bitmap markerBitmapOrange; + private Bitmap markerBitmapRed; + private Bitmap markerBitmapYellow; + private Bitmap markerBitmapTeal; + private Bitmap markerBitmapPurple; + + private Paint bitmapPaintDestBlue; + private Paint bitmapPaintDestGreen; + private Paint bitmapPaintDestOrange; + private Paint bitmapPaintDestRed; + private Paint bitmapPaintDestYellow; + private Paint bitmapPaintDestTeal; + private Paint bitmapPaintDestPurple; + private Bitmap arrowLight; + private Bitmap arrowToDestination; + private Bitmap arrowShadow; + private float[] calculations = new float[2]; + + private final TextPaint textPaint = new TextPaint(); + private final RenderingLineAttributes lineAttrs = new RenderingLineAttributes("measureDistanceLine"); + private final RenderingLineAttributes textAttrs = new RenderingLineAttributes("rulerLineFont"); + private final RenderingLineAttributes planRouteAttrs = new RenderingLineAttributes("markerPlanRouteline"); + private TrkSegment route; + + private float textSize; + private int verticalOffset; + + private List tx = new ArrayList<>(); + private List ty = new ArrayList<>(); + private Path linePath = new Path(); + + private LatLon fingerLocation; + private boolean hasMoved; + private boolean moving; + private boolean useFingerLocation; + private GestureDetector longTapDetector; + private Handler handler; + + private ContextMenuLayer contextMenuLayer; + + private boolean inPlanRouteMode; + private boolean defaultAppMode = true; + + private List amenities = new ArrayList<>(); + + public MapMarkersMiniLayer(OsmandApplication app) { + this.application = app; + } + + public MapMarkersWidgetsMiniFactory getWidgetsFactory() { + return widgetsFactory; + } + + public boolean isInPlanRouteMode() { + return inPlanRouteMode; + } + + public void setInPlanRouteMode(boolean inPlanRouteMode) { + this.inPlanRouteMode = inPlanRouteMode; + } + + public void setDefaultAppMode(boolean defaultAppMode) { + this.defaultAppMode = defaultAppMode; + } + + private void initUI() { + bitmapPaint = new Paint(); + bitmapPaint.setAntiAlias(true); + bitmapPaint.setFilterBitmap(true); + markerBitmapBlue = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_blue); + markerBitmapGreen = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_green); + markerBitmapOrange = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_orange); + markerBitmapRed = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_red); + markerBitmapYellow = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_yellow); + markerBitmapTeal = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_teal); + markerBitmapPurple = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_purple); + + arrowLight = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_direction_arrow_p1_light); + arrowToDestination = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_direction_arrow_p2_color); + arrowShadow = BitmapFactory.decodeResource(view.getResources(), R.drawable.map_marker_direction_arrow_p3_shadow); + bitmapPaintDestBlue = createPaintDest(R.color.marker_blue); + bitmapPaintDestGreen = createPaintDest(R.color.marker_green); + bitmapPaintDestOrange = createPaintDest(R.color.marker_orange); + bitmapPaintDestRed = createPaintDest(R.color.marker_red); + bitmapPaintDestYellow = createPaintDest(R.color.marker_yellow); + bitmapPaintDestTeal = createPaintDest(R.color.marker_teal); + bitmapPaintDestPurple = createPaintDest(R.color.marker_purple); + + widgetsFactory = new MapMarkersWidgetsMiniFactory(application); + + //contextMenuLayer = view.getLayerByClass(ContextMenuLayer.class); + + textSize = application.getResources().getDimensionPixelSize(R.dimen.guide_line_text_size); + verticalOffset = application.getResources().getDimensionPixelSize(R.dimen.guide_line_vertical_offset); + } + + private Paint createPaintDest(int colorId) { + Paint paint = new Paint(); + paint.setDither(true); + paint.setAntiAlias(true); + paint.setFilterBitmap(true); + int color = ContextCompat.getColor(application, colorId); + paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)); + return paint; + } + + private Paint getMarkerDestPaint(int colorIndex) { + switch (colorIndex) { + case 0: + return bitmapPaintDestBlue; + case 1: + return bitmapPaintDestGreen; + case 2: + return bitmapPaintDestOrange; + case 3: + return bitmapPaintDestRed; + case 4: + return bitmapPaintDestYellow; + case 5: + return bitmapPaintDestTeal; + case 6: + return bitmapPaintDestPurple; + default: + return bitmapPaintDestBlue; + } + } + + private Bitmap getMapMarkerBitmap(int colorIndex) { + switch (colorIndex) { + case 0: + return markerBitmapBlue; + case 1: + return markerBitmapGreen; + case 2: + return markerBitmapOrange; + case 3: + return markerBitmapRed; + case 4: + return markerBitmapYellow; + case 5: + return markerBitmapTeal; + case 6: + return markerBitmapPurple; + default: + return markerBitmapBlue; + } + } + + public void setRoute(TrkSegment route) { + this.route = route; + } + + @Override + public void initLayer(OsmandMapTileMiniView view) { + this.view = view; + //handler = new Handler(); + initUI(); +// longTapDetector = new GestureDetector(view.getContext(), new GestureDetector.SimpleOnGestureListener() { +// @Override +// public void onLongPress(MotionEvent e) { +// cancelFingerAction(); +// } +// }); + } + + @Override + public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings nightMode) { + OsmandApplication app = application; + OsmandSettings settings = app.getSettings(); + if (!settings.SHOW_MAP_MARKERS.get()) { + return; + } + + Location myLoc; + if (useFingerLocation && fingerLocation != null) { + myLoc = new Location(""); + myLoc.setLatitude(fingerLocation.getLatitude()); + myLoc.setLongitude(fingerLocation.getLongitude()); + } else { + myLoc = app.getLocationProvider().getLastStaleKnownLocation(); + } + MapMarkersHelper markersHelper = app.getMapMarkersHelper(); + List activeMapMarkers = markersHelper.getMapMarkers(); + int displayedWidgets = settings.DISPLAYED_MARKERS_WIDGETS_COUNT.get(); + + if (route != null && route.points.size() > 0) { + planRouteAttrs.updatePaints(app, nightMode, tileBox); + new Renderable.StandardTrack(new ArrayList<>(route.points), 17.2). + drawSegment(view.getZoom(), defaultAppMode ? planRouteAttrs.paint : planRouteAttrs.paint2, canvas, tileBox); + } + + if (settings.SHOW_LINES_TO_FIRST_MARKERS.get() && myLoc != null) { + textAttrs.paint.setTextSize(textSize); + textAttrs.paint2.setTextSize(textSize); + + lineAttrs.updatePaints(app, nightMode, tileBox); + textAttrs.updatePaints(app, nightMode, tileBox); + textAttrs.paint.setStyle(Paint.Style.FILL); + + textPaint.set(textAttrs.paint); + + boolean drawMarkerName = settings.DISPLAYED_MARKERS_WIDGETS_COUNT.get() == 1; + + float locX; + float locY; + if (app.getMapViewTrackingUtilities().isMapLinkedToLocation() + && !MapViewTrackingUtilities.isSmallSpeedForAnimation(myLoc) + && !app.getMapViewTrackingUtilities().isMovingToMyLocation()) { + locX = tileBox.getPixXFromLatLon(tileBox.getLatitude(), tileBox.getLongitude()); + locY = tileBox.getPixYFromLatLon(tileBox.getLatitude(), tileBox.getLongitude()); + } else { + locX = tileBox.getPixXFromLatLon(myLoc.getLatitude(), myLoc.getLongitude()); + locY = tileBox.getPixYFromLatLon(myLoc.getLatitude(), myLoc.getLongitude()); + } + int[] colors = MapMarker.getColors(application); + for (int i = 0; i < activeMapMarkers.size() && i < displayedWidgets; i++) { + MapMarker marker = activeMapMarkers.get(i); + float markerX = tileBox.getPixXFromLatLon(marker.getLatitude(), marker.getLongitude()); + float markerY = tileBox.getPixYFromLatLon(marker.getLatitude(), marker.getLongitude()); + + linePath.reset(); + tx.clear(); + ty.clear(); + + tx.add(locX); + ty.add(locY); + tx.add(markerX); + ty.add(markerY); + + GeometryWay.calculatePath(tileBox, tx, ty, linePath); + PathMeasure pm = new PathMeasure(linePath, false); + float[] pos = new float[2]; + pm.getPosTan(pm.getLength() / 2, pos, null); + + float dist = (float) MapUtils.getDistance(myLoc.getLatitude(), myLoc.getLongitude(), marker.getLatitude(), marker.getLongitude()); + String distSt = OsmAndFormatter.getFormattedDistance(dist, view.getApplication()); + String text = distSt + (drawMarkerName ? " • " + marker.getName(application) : ""); + text = TextUtils.ellipsize(text, textPaint, pm.getLength(), TextUtils.TruncateAt.END).toString(); + Rect bounds = new Rect(); + textAttrs.paint.getTextBounds(text, 0, text.length(), bounds); + float hOffset = pm.getLength() / 2 - bounds.width() / 2; + lineAttrs.paint.setColor(colors[marker.colorIndex]); + + canvas.rotate(-tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); + canvas.drawPath(linePath, lineAttrs.paint); + if (locX >= markerX) { + canvas.rotate(180, pos[0], pos[1]); + canvas.drawTextOnPath(text, linePath, hOffset, bounds.height() + verticalOffset, textAttrs.paint2); + canvas.drawTextOnPath(text, linePath, hOffset, bounds.height() + verticalOffset, textAttrs.paint); + canvas.rotate(-180, pos[0], pos[1]); + } else { + canvas.drawTextOnPath(text, linePath, hOffset, -verticalOffset, textAttrs.paint2); + canvas.drawTextOnPath(text, linePath, hOffset, -verticalOffset, textAttrs.paint); + } + canvas.rotate(tileBox.getRotate(), tileBox.getCenterPixelX(), tileBox.getCenterPixelY()); + } + } + } + + @Override + public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings nightMode) { + //widgetsFactory.updateInfo(useFingerLocation ? fingerLocation : null, tileBox.getZoom()); + OsmandSettings settings = application.getSettings(); + + if (tileBox.getZoom() < 3 || !settings.SHOW_MAP_MARKERS.get()) { + return; + } + + int displayedWidgets = settings.DISPLAYED_MARKERS_WIDGETS_COUNT.get(); + + MapMarkersHelper markersHelper = application.getMapMarkersHelper(); + + for (MapMarker marker : markersHelper.getMapMarkers()) { + if (isLocationVisible(tileBox, marker) && !overlappedByWaypoint(marker) + && !isInMotion(marker) && !isSynced(marker)) { + Bitmap bmp = getMapMarkerBitmap(marker.colorIndex); + int marginX = bmp.getWidth() / 6; + int marginY = bmp.getHeight(); + int locationX = tileBox.getPixXFromLonNoRot(marker.getLongitude()); + int locationY = tileBox.getPixYFromLatNoRot(marker.getLatitude()); + canvas.rotate(-tileBox.getRotate(), locationX, locationY); + canvas.drawBitmap(bmp, locationX - marginX, locationY - marginY, bitmapPaint); + canvas.rotate(tileBox.getRotate(), locationX, locationY); + } + } + + if (settings.SHOW_ARROWS_TO_FIRST_MARKERS.get()) { + LatLon loc = tileBox.getCenterLatLon(); + int i = 0; + for (MapMarker marker : markersHelper.getMapMarkers()) { + if (!isLocationVisible(tileBox, marker) && !isInMotion(marker)) { + canvas.save(); + net.osmand.Location.distanceBetween(loc.getLatitude(), loc.getLongitude(), + marker.getLatitude(), marker.getLongitude(), calculations); + float bearing = calculations[1] - 90; + float radiusBearing = DIST_TO_SHOW * tileBox.getDensity(); + final QuadPoint cp = tileBox.getCenterPixelPoint(); + canvas.rotate(bearing, cp.x, cp.y); + canvas.translate(-24 * tileBox.getDensity() + radiusBearing, -22 * tileBox.getDensity()); + canvas.drawBitmap(arrowShadow, cp.x, cp.y, bitmapPaint); + canvas.drawBitmap(arrowToDestination, cp.x, cp.y, getMarkerDestPaint(marker.colorIndex)); + canvas.drawBitmap(arrowLight, cp.x, cp.y, bitmapPaint); + canvas.restore(); + } + i++; + if (i > displayedWidgets - 1) { + break; + } + } + } + +// if (contextMenuLayer.getMoveableObject() instanceof MapMarker) { +// MapMarker objectInMotion = (MapMarker) contextMenuLayer.getMoveableObject(); +// PointF pf = contextMenuLayer.getMovableCenterPoint(tileBox); +// Bitmap bitmap = getMapMarkerBitmap(objectInMotion.colorIndex); +// int marginX = bitmap.getWidth() / 6; +// int marginY = bitmap.getHeight(); +// float locationX = pf.x; +// float locationY = pf.y; +// canvas.rotate(-tileBox.getRotate(), locationX, locationY); +// canvas.drawBitmap(bitmap, locationX - marginX, locationY - marginY, bitmapPaint); +// +// } + } + + private boolean isSynced(@NonNull MapMarker marker) { + return marker.wptPt != null || marker.favouritePoint != null; + } + + private boolean isInMotion(@NonNull MapMarker marker) { + return marker.equals(contextMenuLayer.getMoveableObject()); + } + + public boolean isLocationVisible(RotatedTileBox tb, MapMarker marker) { + //noinspection SimplifiableIfStatement + if (marker == null || tb == null) { + return false; + } + return containsLatLon(tb, marker.getLatitude(), marker.getLongitude()); + } + + public boolean containsLatLon(RotatedTileBox tb, double lat, double lon) { + double widgetHeight = 0; + if (widgetsFactory.isTopBarVisible()) { + widgetHeight = widgetsFactory.getTopBarHeight(); + } + double tx = tb.getPixXFromLatLon(lat, lon); + double ty = tb.getPixYFromLatLon(lat, lon); + return tx >= 0 && tx <= tb.getPixWidth() && ty >= widgetHeight && ty <= tb.getPixHeight(); + } + + public boolean overlappedByWaypoint(MapMarker marker) { + List targetPoints = application.getTargetPointsHelper().getAllPoints(); + for (TargetPoint t : targetPoints) { + if (t.point.equals(marker.point)) { + return true; + } + } + return false; + } + + @Override + public void destroyLayer() { + } + + @Override + public boolean onTouchEvent(MotionEvent event, RotatedTileBox tileBox) { + if (!longTapDetector.onTouchEvent(event)) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + float x = event.getX(); + float y = event.getY(); + fingerLocation = tileBox.getLatLonFromPixel(x, y); + hasMoved = false; + moving = true; + break; + + case MotionEvent.ACTION_MOVE: + if (!hasMoved) { + if (!handler.hasMessages(MAP_REFRESH_MESSAGE)) { + Message msg = Message.obtain(handler, new Runnable() { + @Override + public void run() { + handler.removeMessages(MAP_REFRESH_MESSAGE); + if (moving) { + if (!useFingerLocation) { + useFingerLocation = true; + } + } + } + }); + msg.what = MAP_REFRESH_MESSAGE; + handler.sendMessageDelayed(msg, USE_FINGER_LOCATION_DELAY); + } + hasMoved = true; + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + cancelFingerAction(); + break; + } + } + return super.onTouchEvent(event, tileBox); + } + + private void cancelFingerAction() { + handler.removeMessages(MAP_REFRESH_MESSAGE); + useFingerLocation = false; + moving = false; + fingerLocation = null; + } + + @Override + public boolean drawInScreenPixels() { + return false; + } + + @Override + public boolean disableSingleTap() { + return inPlanRouteMode; + } + + @Override + public boolean disableLongPressOnMap() { + return inPlanRouteMode; + } + + @Override + public boolean isObjectClickable(Object o) { + return false; + } + + @Override + public boolean runExclusiveAction(Object o, boolean unknownLocation) { + OsmandSettings settings = application.getSettings(); + if (unknownLocation + || o == null + || !(o instanceof MapMarker) + || !settings.SELECT_MARKER_ON_SINGLE_TAP.get() + || !settings.SHOW_MAP_MARKERS.get()) { + return false; + } + final MapMarkersHelper helper = application.getMapMarkersHelper(); + final MapMarker old = helper.getMapMarkers().get(0); + helper.moveMarkerToTop((MapMarker) o); + String title = application.getString(R.string.marker_activated, helper.getMapMarkers().get(0).getName(application)); + return true; + } + + @Override + public void collectObjectsFromPoint(PointF point, RotatedTileBox tileBox, List o, boolean unknownLocation) { + if (tileBox.getZoom() < 3 || !application.getSettings().SHOW_MAP_MARKERS.get()) { + return; + } + amenities.clear(); + OsmandApplication app = application; + int r = getDefaultRadiusPoi(tileBox); + boolean selectMarkerOnSingleTap = app.getSettings().SELECT_MARKER_ON_SINGLE_TAP.get(); + + for (MapMarker marker : app.getMapMarkersHelper().getMapMarkers()) { + if ((!unknownLocation && selectMarkerOnSingleTap) || !isSynced(marker)) { + LatLon latLon = marker.point; + if (latLon != null) { + int x = (int) tileBox.getPixXFromLatLon(latLon.getLatitude(), latLon.getLongitude()); + int y = (int) tileBox.getPixYFromLatLon(latLon.getLatitude(), latLon.getLongitude()); + + if (calculateBelongs((int) point.x, (int) point.y, x, y, r)) { + if (!unknownLocation && selectMarkerOnSingleTap) { + o.add(marker); + } else { + if (isMarkerOnFavorite(marker) && app.getSettings().SHOW_FAVORITES.get() + || isMarkerOnWaypoint(marker) && app.getSettings().SHOW_WPT.get()) { + continue; + } + Amenity mapObj = getMapObjectByMarker(marker); + if (mapObj != null) { + amenities.add(mapObj); + o.add(mapObj); + } else { + o.add(marker); + } + } + } + } + } + } + } + + private boolean isMarkerOnWaypoint(@NonNull MapMarker marker) { + return marker.point != null && application.getSelectedGpxHelper().getVisibleWayPointByLatLon(marker.point) != null; + } + + private boolean isMarkerOnFavorite(@NonNull MapMarker marker) { + return marker.point != null && application.getFavorites().getVisibleFavByLatLon(marker.point) != null; + } + + @Nullable + public Amenity getMapObjectByMarker(@NonNull MapMarker marker) { + if (marker.mapObjectName != null && marker.point != null) { + String mapObjName = marker.mapObjectName.split("_")[0]; + return findAmenity(application, -1, Collections.singletonList(mapObjName), marker.point, 15); + } + return null; + } + + private boolean calculateBelongs(int ex, int ey, int objx, int objy, int radius) { + return Math.abs(objx - ex) <= radius * 1.5 && (ey - objy) <= radius * 1.5 && (objy - ey) <= 2.5 * radius; + } + + @Override + public LatLon getObjectLocation(Object o) { + if (o instanceof MapMarker) { + return ((MapMarker) o).point; + } else if (o instanceof Amenity && amenities.contains(o)) { + return ((Amenity) o).getLocation(); + } + return null; + } + + + @Override + public PointDescription getObjectName(Object o) { +// if (o instanceof MapMarker) { +// return ((MapMarker) o).getPointDescription(view.getContext()); +// } + return null; + } + + @Override + public int getOrder(Object o) { + return 0; + } + + @Override + public void setSelectedObject(Object o) { + } + + @Override + public void clearSelectedObject() { + } + + @Override + public boolean isObjectMovable(Object o) { + return o instanceof MapMarker; + } + + @Override + public void applyNewObjectPosition(@NonNull Object o, @NonNull LatLon position, + @Nullable ApplyMovedObjectCallback callback) { + boolean result = false; + MapMarker newObject = null; + if (o instanceof MapMarker) { + MapMarkersHelper markersHelper = application.getMapMarkersHelper(); + MapMarker marker = (MapMarker) o; + + PointDescription originalDescription = marker.getOriginalPointDescription(); + if (originalDescription.isLocation()) { + originalDescription.setName(PointDescription.getSearchAddressStr(application)); + } + markersHelper.moveMapMarker(marker, position); + int index = markersHelper.getMapMarkers().indexOf(marker); + if (index != -1) { + newObject = markersHelper.getMapMarkers().get(index); + } + result = true; + } + if (callback != null) { + callback.onApplyMovedObject(result, newObject == null ? o : newObject); + } + } +} + diff --git a/OsmAnd/src/net/osmand/plus/server/map/MapMarkersWidgetsMiniFactory.java b/OsmAnd/src/net/osmand/plus/server/map/MapMarkersWidgetsMiniFactory.java new file mode 100644 index 0000000000..125834a291 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/server/map/MapMarkersWidgetsMiniFactory.java @@ -0,0 +1,306 @@ +package net.osmand.plus.server.map; + +import android.view.View; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; +import net.osmand.Location; +import net.osmand.data.LatLon; +import net.osmand.data.PointDescription; +import net.osmand.plus.*; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.mapmarkers.MapMarkersDialogFragment; +import net.osmand.plus.views.AnimateDraggingMapThread; +import net.osmand.plus.views.DirectionDrawable; +import net.osmand.plus.views.OsmandMapLayer; +import net.osmand.plus.views.OsmandMapTileView; +import net.osmand.plus.views.mapwidgets.widgets.TextInfoWidget; +import net.osmand.util.Algorithms; +import net.osmand.util.MapUtils; + +import java.util.List; +public class MapMarkersWidgetsMiniFactory { + + public static final int MIN_DIST_OK_VISIBLE = 40; // meters + public static final int MIN_DIST_2ND_ROW_SHOW = 150; // meters + private static OsmandApplication app; + + private MapMarkersHelper helper; + private int screenOrientation; + private boolean portraitMode; + + + private LatLon loc; + + private boolean cachedTopBarVisibility; + + public MapMarkersWidgetsMiniFactory(final OsmandApplication application) { + this.app = application; + helper = app.getMapMarkersHelper(); + screenOrientation = app.getUIUtilities().getScreenOrientation(); + //portraitMode = AndroidUiHelper.isOrientationPortrait(map); + + updateVisibility(false); + } + + private void removeMarker(int index) { + if (helper.getMapMarkers().size() > index) { + helper.moveMapMarkerToHistory(helper.getMapMarkers().get(index)); + } + } + + private void showMarkerOnMap(int index) { +// if (helper.getMapMarkers().size() > index) { +// MapMarkersHelper.MapMarker marker = helper.getMapMarkers().get(index); +// AnimateDraggingMapThread thread = app.getMapView().getAnimatedDraggingThread(); +// LatLon pointToNavigate = marker.point; +// if (pointToNavigate != null) { +// int fZoom = map.getMapView().getZoom() < 15 ? 15 : map.getMapView().getZoom(); +// thread.startMoving(pointToNavigate.getLatitude(), pointToNavigate.getLongitude(), fZoom, true); +// } +// //MapMarkerDialogHelper.showMarkerOnMap(map, marker); +// } + } + + public boolean updateVisibility(boolean visible) { + return visible; + } + + public int getTopBarHeight() { + return 0; + } + + public boolean isTopBarVisible() { + return false; + } + + public void updateInfo(LatLon customLocation, int zoom) { + if (customLocation != null) { + loc = customLocation; + } else { + Location l = app.getLocationProvider().getLastStaleKnownLocation(); + if (l != null) { + loc = new LatLon(l.getLatitude(), l.getLongitude()); + } else { + } + } + + List markers = helper.getMapMarkers(); + if (zoom < 3 || markers.size() == 0 + || !app.getSettings().MARKERS_DISTANCE_INDICATION_ENABLED.get() + || !app.getSettings().MAP_MARKERS_MODE.get().isToolbar() + || app.getRoutingHelper().isFollowingMode() + || app.getRoutingHelper().isRoutePlanningMode()) { + updateVisibility(false); + return; + } + + Float heading = app.getMapViewTrackingUtilities().getHeading(); + MapMarkersHelper.MapMarker marker = markers.get(0); + + if (markers.size() > 1 && app.getSettings().DISPLAYED_MARKERS_WIDGETS_COUNT.get() == 2) { + marker = markers.get(1); + if (loc != null && customLocation == null) { + for (int i = 1; i < markers.size(); i++) { + MapMarkersHelper.MapMarker m = markers.get(i); + m.dist = (int) (MapUtils.getDistance(m.getLatitude(), m.getLongitude(), + loc.getLatitude(), loc.getLongitude())); + if (m.dist < MIN_DIST_2ND_ROW_SHOW && marker.dist > m.dist) { + marker = m; + } + } + } + } else { + } + + updateVisibility(true); + } + + private void updateUI(LatLon loc, Float heading, MapMarkersHelper.MapMarker marker, ImageView arrowImg, + TextView distText, ImageButton okButton, TextView addressText, + boolean firstLine, boolean customLocation) { + float[] mes = new float[2]; + if (loc != null && marker.point != null) { + Location.distanceBetween(marker.getLatitude(), marker.getLongitude(), loc.getLatitude(), loc.getLongitude(), mes); + } + + if (customLocation) { + heading = 0f; + } + + boolean newImage = false; + DirectionDrawable dd; + if (!(arrowImg.getDrawable() instanceof DirectionDrawable)) { + newImage = true; + dd = new DirectionDrawable(app, arrowImg.getWidth(), arrowImg.getHeight()); + } else { + dd = (DirectionDrawable) arrowImg.getDrawable(); + } + dd.setImage(R.drawable.ic_arrow_marker_diretion, MapMarkersHelper.MapMarker.getColorId(marker.colorIndex)); + if (heading != null && loc != null) { + dd.setAngle(mes[1] - heading + 180 + screenOrientation); + } + if (newImage) { + arrowImg.setImageDrawable(dd); + } + arrowImg.invalidate(); + + int dist = (int) mes[0]; + String txt; + if (loc != null) { + txt = OsmAndFormatter.getFormattedDistance(dist, app); + } else { + txt = "—"; + } + if (txt != null) { + distText.setText(txt); + } + AndroidUiHelper.updateVisibility(okButton, !customLocation && loc != null && dist < MIN_DIST_OK_VISIBLE); + + String descr; + PointDescription pd = marker.getPointDescription(app); + if (Algorithms.isEmpty(pd.getName())) { + descr = pd.getTypeName(); + } else { + descr = pd.getName(); + } + if (!firstLine && !isLandscapeLayout()) { + descr = " • " + descr; + } + + addressText.setText(descr); + } + + public TextInfoWidget createMapMarkerControl(final MapActivity map, final boolean firstMarker) { + return new net.osmand.plus.views.mapwidgets.MapMarkersWidgetsFactory.DistanceToMapMarkerControl(map, firstMarker) { + @Override + public LatLon getLatLon() { + return loc; + } + + @Override + protected void click(OsmandMapTileView view) { + showMarkerOnMap(firstMarker ? 0 : 1); + } + }; + } + + public boolean isLandscapeLayout() { + return !portraitMode; + } + + public abstract static class DistanceToMapMarkerControl extends TextInfoWidget { + + private boolean firstMarker; + private final OsmandMapTileView view; + private MapActivity map; + private MapMarkersHelper helper; + private float[] calculations = new float[1]; + private int cachedMeters; + private int cachedMarkerColorIndex = -1; + private Boolean cachedNightMode = null; + + public DistanceToMapMarkerControl(MapActivity map, boolean firstMarker) { + super(map); + this.map = map; + this.firstMarker = firstMarker; + this.view = map.getMapView(); + helper = app.getMapMarkersHelper(); + setText(null, null); + setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + click(view); + } + }); + } + + protected abstract void click(OsmandMapTileView view); + + public abstract LatLon getLatLon(); + + @Override + public boolean updateInfo(OsmandMapLayer.DrawSettings drawSettings) { + MapMarkersHelper.MapMarker marker = getMarker(); + if (marker == null + || app.getRoutingHelper().isRoutePlanningMode() + || app.getRoutingHelper().isFollowingMode()) { + cachedMeters = 0; + setText(null, null); + return false; + } + boolean res = false; + int d = getDistance(); + if (isUpdateNeeded() || cachedMeters != d) { + cachedMeters = d; + String ds = OsmAndFormatter.getFormattedDistance(cachedMeters, app); + int ls = ds.lastIndexOf(' '); + if (ls == -1) { + setText(ds, null); + } else { + setText(ds.substring(0, ls), ds.substring(ls + 1)); + } + res = true; + } + + if (marker.colorIndex != -1) { + if (marker.colorIndex != cachedMarkerColorIndex + || cachedNightMode == null || cachedNightMode != isNight()) { + setImageDrawable(app.getUIUtilities() + .getLayeredIcon(isNight() ? R.drawable.widget_marker_night : R.drawable.widget_marker_day, + R.drawable.widget_marker_triangle, 0, + MapMarkersHelper.MapMarker.getColorId(marker.colorIndex))); + cachedMarkerColorIndex = marker.colorIndex; + cachedNightMode = isNight(); + res = true; + } + } + return res; + } + + @Override + public boolean isMetricSystemDepended() { + return true; + } + + public LatLon getPointToNavigate() { + MapMarkersHelper.MapMarker marker = getMarker(); + if (marker != null) { + return marker.point; + } + return null; + } + + private MapMarkersHelper.MapMarker getMarker() { + List markers = helper.getMapMarkers(); + if (firstMarker) { + if (markers.size() > 0) { + return markers.get(0); + } + } else { + if (markers.size() > 1) { + return markers.get(1); + } + } + return null; + } + + public int getDistance() { + int d = 0; + LatLon l = getPointToNavigate(); + if (l != null) { + LatLon loc = getLatLon(); + if (loc == null) { + Location.distanceBetween(view.getLatitude(), view.getLongitude(), l.getLatitude(), l.getLongitude(), calculations); + } else { + Location.distanceBetween(loc.getLatitude(), loc.getLongitude(), l.getLatitude(), l.getLongitude(), calculations); + } + d = (int) calculations[0]; + } + return d; + } + } +} + diff --git a/OsmAnd/src/net/osmand/plus/server/map/MapTextMiniLayer.java b/OsmAnd/src/net/osmand/plus/server/map/MapTextMiniLayer.java new file mode 100644 index 0000000000..67f205ad3e --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/server/map/MapTextMiniLayer.java @@ -0,0 +1,223 @@ +package net.osmand.plus.server.map; + +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.text.TextUtils; +import gnu.trove.set.hash.TIntHashSet; +import net.osmand.data.LatLon; +import net.osmand.data.RotatedTileBox; +import net.osmand.plus.R; +import net.osmand.plus.views.OsmandMapLayer; +import net.osmand.plus.views.OsmandMapTileView; + +import java.util.*; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Paint.Style; +import android.text.TextUtils; + +import net.osmand.data.LatLon; +import net.osmand.data.RotatedTileBox; + +import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; + +import gnu.trove.set.hash.TIntHashSet; +import net.osmand.plus.R; +import net.osmand.plus.views.OsmandMapLayer; +import net.osmand.plus.views.OsmandMapTileView; + +public class MapTextMiniLayer extends OsmandMapLayer { + + private static final int TEXT_WRAP = 15; + private static final int TEXT_LINES = 3; + private static final int TEXT_SIZE = 13; + + private Map> textObjects = new LinkedHashMap<>(); + private Paint paintTextIcon; + private OsmandMapTileView view; + + public interface MapTextProvider { + + LatLon getTextLocation(T o); + + int getTextShift(T o, RotatedTileBox rb); + + String getText(T o); + + boolean isTextVisible(); + + boolean isFakeBoldText(); + } + + public void putData(OsmandMapLayer ml, Collection objects) { + if (objects == null || objects.isEmpty()) { + textObjects.remove(ml); + } else { + if (ml instanceof net.osmand.plus.views.layers.MapTextLayer.MapTextProvider) { + textObjects.put(ml, objects); + } else { + throw new IllegalArgumentException(); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { + TIntHashSet set = new TIntHashSet(); + for (OsmandMapLayer l : textObjects.keySet()) { + net.osmand.plus.views.layers.MapTextLayer.MapTextProvider provider = (net.osmand.plus.views.layers.MapTextLayer.MapTextProvider) l; + if (!view.isLayerVisible(l) || !provider.isTextVisible()) { + continue; + } + + updateTextSize(); + paintTextIcon.setFakeBoldText(provider.isFakeBoldText()); + for (Object o : textObjects.get(l)) { + LatLon loc = provider.getTextLocation(o); + String name = provider.getText(o); + if (loc == null || TextUtils.isEmpty(name)) { + continue; + } + + int x = (int) tileBox.getPixXFromLatLon(loc.getLatitude(), loc.getLongitude()); + int y = (int) tileBox.getPixYFromLatLon(loc.getLatitude(), loc.getLongitude()); + int tx = tileBox.getPixXFromLonNoRot(loc.getLongitude()); + int ty = tileBox.getPixYFromLatNoRot(loc.getLatitude()); + + int lines = 0; + while (lines < TEXT_LINES) { + if (set.contains(division(tx, ty, 0, lines)) || set.contains(division(tx, ty, -1, lines)) + || set.contains(division(tx, ty, +1, lines))) { + break; + } + lines++; + } + if (lines != 0) { + int r = provider.getTextShift(o, tileBox); + drawWrappedText(canvas, name, paintTextIcon.getTextSize(), x, + y + r + 2 + paintTextIcon.getTextSize() / 2, lines); + while (lines > 0) { + set.add(division(tx, ty, 1, lines - 1)); + set.add(division(tx, ty, -1, lines - 1)); + set.add(division(tx, ty, 0, lines - 1)); + lines--; + } + } + } + } + } + + private int division(int x, int y, int sx, int sy) { + // make numbers positive + return ((((x + 10000) >> 4) + sx) << 16) | (((y + 10000) >> 4) + sy); + } + + private void drawWrappedText(Canvas cv, String text, float textSize, float x, float y, int lines) { + boolean nightMode = view.getApplication().getDaynightHelper().isNightMode(); + if (text.length() > TEXT_WRAP) { + int start = 0; + int end = text.length(); + int lastSpace = -1; + int line = 0; + int pos = 0; + int limit = 0; + while (pos < end && (line < lines)) { + lastSpace = -1; + limit += TEXT_WRAP; + while (pos < limit && pos < end) { + if (Character.isWhitespace(text.charAt(pos))) { + lastSpace = pos; + } + pos++; + } + if (lastSpace == -1 || (pos == end)) { + drawShadowText(cv, text.substring(start, pos), x, y + line * (textSize + 2), nightMode); + start = pos; + } else { + String subtext = text.substring(start, lastSpace); + if (line + 1 == lines) { + subtext += ".."; + } + drawShadowText(cv, subtext, x, y + line * (textSize + 2), nightMode); + + start = lastSpace + 1; + limit += (start - pos) - 1; + } + + line++; + } + } else { + drawShadowText(cv, text, x, y, nightMode); + } + } + + private void drawShadowText(Canvas cv, String text, float centerX, float centerY, boolean nightMode) { + Resources r = view.getApplication().getResources(); + paintTextIcon.setStyle(Paint.Style.STROKE); + paintTextIcon.setColor(nightMode + ? r.getColor(R.color.widgettext_shadow_night) + : r.getColor(R.color.widgettext_shadow_day)); + paintTextIcon.setStrokeWidth(2); + cv.drawText(text, centerX, centerY, paintTextIcon); + // reset + paintTextIcon.setStrokeWidth(2); + paintTextIcon.setStyle(Paint.Style.FILL); + paintTextIcon.setColor(nightMode + ? r.getColor(R.color.widgettext_night) + : r.getColor(R.color.widgettext_day)); + cv.drawText(text, centerX, centerY, paintTextIcon); + } + + @Override + public void initLayer(OsmandMapTileView v) { + this.view = v; + paintTextIcon = new Paint(); + updateTextSize(); + paintTextIcon.setTextAlign(Paint.Align.CENTER); + paintTextIcon.setAntiAlias(true); + Map> textObjectsLoc = new TreeMap<>(new Comparator() { + @Override + public int compare(OsmandMapLayer lhs, OsmandMapLayer rhs) { + if (view != null) { + float z1 = view.getZorder(lhs); + float z2 = view.getZorder(rhs); + return Float.compare(z1, z2); + } + return 0; + } + }); + textObjectsLoc.putAll(this.textObjects); + this.textObjects = textObjectsLoc; + } + + @Override + public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { + } + + @Override + public void destroyLayer() { + } + + @Override + public boolean drawInScreenPixels() { + return true; + } + + private void updateTextSize() { + float scale = view.getApplication().getSettings().TEXT_SCALE.get(); + float textSize = scale * TEXT_SIZE * view.getDensity(); + if (paintTextIcon.getTextSize() != textSize) { + paintTextIcon.setTextSize(textSize); + } + } +} + diff --git a/OsmAnd/src/net/osmand/plus/server/map/MapTileMiniLayer.java b/OsmAnd/src/net/osmand/plus/server/map/MapTileMiniLayer.java new file mode 100644 index 0000000000..588e91ab8e --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/server/map/MapTileMiniLayer.java @@ -0,0 +1,276 @@ +package net.osmand.plus.server.map; + + +import android.annotation.SuppressLint; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.Build; +import android.widget.Toast; + +import net.osmand.data.QuadRect; +import net.osmand.data.RotatedTileBox; +import net.osmand.map.ITileSource; +import net.osmand.map.TileSourceManager; +import net.osmand.map.TileSourceManager.TileSourceTemplate; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.R; +import net.osmand.plus.mapillary.MapillaryPlugin; +import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin; +import net.osmand.plus.resources.ResourceManager; +import net.osmand.plus.views.BaseMapLayer; +import net.osmand.plus.views.MapTileAdapter; +import net.osmand.plus.views.OsmandMapTileView; +import net.osmand.plus.views.YandexTrafficAdapter; +import net.osmand.util.MapUtils; + +public class MapTileMiniLayer extends OsmandMapMiniLayer { + + protected static final int emptyTileDivisor = 16; + public static final int OVERZOOM_IN = 2; + + protected final boolean mainMap; + protected ITileSource map = null; + + protected Paint paintBitmap; + protected RectF bitmapToDraw = new RectF(); + protected Rect bitmapToZoom = new Rect(); + + + protected ResourceManager resourceManager; + protected OsmandSettings settings; + private boolean visible = true; + private boolean useSampling; + + + public MapTileMiniLayer(boolean mainMap) { + this.mainMap = mainMap; + } + + @Override + public boolean drawInScreenPixels() { + return false; + } + + @Override + public void initLayer(OsmandMapTileMiniView view) { + settings = view.getSettings(); + resourceManager = view.getApplication().getResourceManager(); + + useSampling = Build.VERSION.SDK_INT < 28; + + paintBitmap = new Paint(); + paintBitmap.setFilterBitmap(true); + + + } + + public void setAlpha(int alpha) { + if (paintBitmap != null) { + paintBitmap.setAlpha(alpha); + } + } + + public void setMapTileAdapter(MapTileAdapter mapTileAdapter) { + + } + + public void setMapForMapTileAdapter(ITileSource map, MapTileAdapter mapTileAdapter) { + + } + + public void setMap(ITileSource map) { + MapTileAdapter target = null; + if (map instanceof TileSourceTemplate) { + if (TileSourceManager.RULE_YANDEX_TRAFFIC.equals(((TileSourceTemplate) map).getRule())) { + map = null; + target = new YandexTrafficAdapter(); + } + + } + this.map = map; + setMapTileAdapter(target); + } + + public MapTileAdapter getMapTileAdapter() { + return null; + } + + @SuppressLint("WrongCall") + @Override + public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, + DrawSettings drawSettings) { + + drawTileMap(canvas, tileBox); + } + + @Override + public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings drawSettings) { + + } + + public void drawTileMap(Canvas canvas, RotatedTileBox tileBox) { + ITileSource map = this.map; + if (map == null) { + return; + } + ResourceManager mgr = resourceManager; + int nzoom = tileBox.getZoom(); + final QuadRect tilesRect = tileBox.getTileBounds(); + + // recalculate for ellipsoid coordinates + float ellipticTileCorrection = 0; + if (map.isEllipticYTile()) { + ellipticTileCorrection = (float) (MapUtils.getTileEllipsoidNumberY(nzoom, tileBox.getLatitude()) - tileBox.getCenterTileY()); + } + + + int left = (int) Math.floor(tilesRect.left); + int top = (int) Math.floor(tilesRect.top + ellipticTileCorrection); + int width = (int) Math.ceil(tilesRect.right - left); + int height = (int) Math.ceil(tilesRect.bottom + ellipticTileCorrection - top); + + boolean useInternet = (OsmandPlugin.getEnabledPlugin(OsmandRasterMapsPlugin.class) != null || OsmandPlugin.getEnabledPlugin(MapillaryPlugin.class) != null) + && settings.isInternetConnectionAvailable() && map.couldBeDownloadedFromInternet(); + int maxLevel = map.getMaximumZoomSupported(); + int tileSize = map.getTileSize(); + boolean oneTileShown = false; + + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + int leftPlusI = left + i; + int topPlusJ = top + j; + + int x1 = tileBox.getPixXFromTileXNoRot(leftPlusI); + int x2 = tileBox.getPixXFromTileXNoRot(leftPlusI + 1); + + int y1 = tileBox.getPixYFromTileYNoRot(topPlusJ - ellipticTileCorrection); + int y2 = tileBox.getPixYFromTileYNoRot(topPlusJ + 1 - ellipticTileCorrection); + bitmapToDraw.set(x1, y1, x2 , y2); + + final int tileX = leftPlusI; + final int tileY = topPlusJ; + Bitmap bmp = null; + String ordImgTile = mgr.calculateTileId(map, tileX, tileY, nzoom); + // asking tile image async + boolean imgExist = mgr.tileExistOnFileSystem(ordImgTile, map, tileX, tileY, nzoom); + boolean originalWillBeLoaded = useInternet && nzoom <= maxLevel; + if (imgExist || originalWillBeLoaded) { + bmp = mgr.getBitmapTilesCache().getTileForMapAsync(ordImgTile, map, tileX, tileY, nzoom, useInternet); + } + if (bmp == null) { + int div = 1; + boolean readFromCache = originalWillBeLoaded || imgExist; + boolean loadIfExists = !readFromCache; + // asking if there is small version of the map (in cache) + int allowedScale = Math.min(OVERZOOM_IN + Math.max(0, nzoom - map.getMaximumZoomSupported()), 8); + int kzoom = 1; + for (; kzoom <= allowedScale; kzoom++) { + div *= 2; + String imgTileId = mgr.calculateTileId(map, tileX / div, tileY / div, nzoom - kzoom); + if (readFromCache) { + bmp = mgr.getBitmapTilesCache().get(imgTileId); + if (bmp != null) { + break; + } + } else if (loadIfExists) { + if (mgr.tileExistOnFileSystem(imgTileId, map, tileX / div, tileY / div, nzoom - kzoom) + || (useInternet && nzoom - kzoom <= maxLevel)) { + bmp = mgr.getBitmapTilesCache().getTileForMapAsync(imgTileId, map, tileX / div, tileY / div, nzoom + - kzoom, useInternet); + break; + } + } + + } + if (bmp != null) { + if (bmp.getWidth() != tileSize && bmp.getWidth() > 0) { + tileSize = bmp.getWidth(); + } + int xZoom = (tileX % div) * tileSize / div; + int yZoom = (tileY % div) * tileSize / div; + // nice scale + boolean useSampling = this.useSampling && kzoom > 3; + bitmapToZoom.set(Math.max(xZoom, 0), Math.max(yZoom, 0), + Math.min(xZoom + tileSize / div, tileSize), + Math.min(yZoom + tileSize / div, tileSize)); + if (!useSampling) { + canvas.drawBitmap(bmp, bitmapToZoom, bitmapToDraw, paintBitmap); + } else { + int margin = 1; + int scaledSize = tileSize / div; + float innerMargin = 0.5f; + RectF src = new RectF(0, 0, scaledSize, scaledSize); + if (bitmapToZoom.left >= margin) { + bitmapToZoom.left -= margin; + src.left = innerMargin; + src.right += margin; + } + if (bitmapToZoom.top >= margin) { + bitmapToZoom.top -= margin; + src.top = innerMargin; + src.bottom += margin; + } + if (bitmapToZoom.right + margin <= tileSize) { + bitmapToZoom.right += margin; + src.right += margin - innerMargin; + } + if (bitmapToZoom.bottom + margin <= tileSize) { + bitmapToZoom.bottom += margin; + src.bottom += margin - innerMargin; + } + Matrix m = new Matrix(); + RectF dest = new RectF(0, 0, tileSize, tileSize); + m.setRectToRect(src, dest, Matrix.ScaleToFit.FILL); + Bitmap sampled = Bitmap.createBitmap(bmp, + bitmapToZoom.left, bitmapToZoom.top, + bitmapToZoom.width(), bitmapToZoom.height(), m, true); + bitmapToZoom.set(0, 0, tileSize, tileSize); + // very expensive that's why put in the cache + mgr.getBitmapTilesCache().put(ordImgTile, sampled); + canvas.drawBitmap(sampled, bitmapToZoom, bitmapToDraw, paintBitmap); + } + } + } else { + bitmapToZoom.set(0, 0, tileSize, tileSize); + canvas.drawBitmap(bmp, bitmapToZoom, bitmapToDraw, paintBitmap); + } + if (bmp != null) { + oneTileShown = true; + } + } + } + } + + + public int getMaximumShownMapZoom() { + return map == null ? 20 : map.getMaximumZoomSupported() + OVERZOOM_IN; + } + + public int getMinimumShownMapZoom() { + return map == null ? 1 : map.getMinimumZoomSupported(); + } + + @Override + public void destroyLayer() { + setMapTileAdapter(null); + } + + public boolean isVisible() { + return visible; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + + public ITileSource getMap() { + return map; + } + +} + diff --git a/OsmAnd/src/net/osmand/plus/server/map/MapVectorMiniLayer.java b/OsmAnd/src/net/osmand/plus/server/map/MapVectorMiniLayer.java new file mode 100644 index 0000000000..35bb264bff --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/server/map/MapVectorMiniLayer.java @@ -0,0 +1,232 @@ +package net.osmand.plus.server.map; + +import android.graphics.*; +import net.osmand.core.android.MapRendererView; +import net.osmand.core.jni.MapLayerConfiguration; +import net.osmand.core.jni.PointI; +import net.osmand.data.LatLon; +import net.osmand.data.QuadPointDouble; +import net.osmand.data.RotatedTileBox; +import net.osmand.plus.render.MapRenderRepositories; +import net.osmand.plus.resources.ResourceManager; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.views.*; +import net.osmand.plus.views.corenative.NativeCoreContext; +import net.osmand.util.Algorithms; +import net.osmand.util.MapUtils; + + import net.osmand.core.android.MapRendererView; + import net.osmand.core.android.TileSourceProxyProvider; + import net.osmand.core.jni.MapLayerConfiguration; + import net.osmand.core.jni.PointI; + import net.osmand.data.LatLon; + import net.osmand.data.QuadPointDouble; + import net.osmand.data.RotatedTileBox; + import net.osmand.map.ITileSource; + import net.osmand.plus.settings.backend.OsmandSettings; + import net.osmand.plus.resources.ResourceManager; + import net.osmand.plus.views.BaseMapLayer; + import net.osmand.plus.views.MapTileLayer; + import net.osmand.plus.views.OsmandMapTileView; + import net.osmand.plus.views.corenative.NativeCoreContext; + import net.osmand.util.Algorithms; + import net.osmand.util.MapUtils; + import android.graphics.Bitmap; + import android.graphics.Canvas; + import android.graphics.Paint; + import android.graphics.PointF; + import android.graphics.RectF; + +public class MapVectorMiniLayer extends OsmandMapMiniLayer { + + public static final int DEFAULT_MAX_ZOOM = 21; + public static final int DEFAULT_MIN_ZOOM = 1; + private int alpha = 255; + protected int warningToSwitchMapShown = 0; + + public int getAlpha() { + return alpha; + } + + private OsmandMapTileMiniView view; + private ResourceManager resourceManager; + private Paint paintImg; + + private RectF destImage = new RectF(); + private final MapTileMiniLayer tileLayer; + private boolean visible = false; + private boolean oldRender = false; + private String cachedUnderlay; + private Integer cachedMapTransparency; + private String cachedOverlay; + private Integer cachedOverlayTransparency; + + public MapVectorMiniLayer(MapTileMiniLayer tileLayer, boolean oldRender) { + this.tileLayer = tileLayer; + this.oldRender = oldRender; + } + + @Override + public void destroyLayer() { + } + + @Override + public boolean drawInScreenPixels() { + return false; + } + + @Override + public void initLayer(OsmandMapTileMiniView view) { + this.view = view; + resourceManager = view.getApplication().getResourceManager(); + paintImg = new Paint(); + paintImg.setFilterBitmap(true); + paintImg.setAlpha(getAlpha()); + } + + public boolean isVectorDataVisible() { + return visible && view.getZoom() >= view.getSettings().LEVEL_TO_SWITCH_VECTOR_RASTER.get(); + } + + public boolean isVisible() { + return visible; + } + + public void setVisible(boolean visible) { + this.visible = visible; + if (!visible) { + resourceManager.getRenderer().clearCache(); + } + } + + @Override + public void onDraw(Canvas canvas, RotatedTileBox tilesRect, DrawSettings drawSettings) { + + } + + @Override + public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tilesRect, DrawSettings drawSettings) { +// if (!visible) { +// return; +// } + // if (!isVectorDataVisible() && tileLayer != null) { + // tileLayer.drawTileMap(canvas, tilesRect); + // resourceManager.getRenderer().interruptLoadingMap(); + // } else { + final MapRendererView mapRenderer = view.getMapRenderer(); + if (mapRenderer != null && !oldRender) { + NativeCoreContext.getMapRendererContext().setNightMode(drawSettings.isNightMode()); + OsmandSettings st = view.getApplication().getSettings(); + /* TODO: Commented to avoid crash (looks like IMapTiledDataProvider.Request parameter does not pass correctly or cannot be resolved while calling obtainImage method) + if (!Algorithms.objectEquals(st.MAP_UNDERLAY.get(), cachedUnderlay)) { + cachedUnderlay = st.MAP_UNDERLAY.get(); + ITileSource tileSource = st.getTileSourceByName(cachedUnderlay, false); + if (tileSource != null) { + TileSourceProxyProvider prov = new TileSourceProxyProvider(view.getApplication(), tileSource); + mapRenderer.setMapLayerProvider(-1, prov.instantiateProxy(true)); + prov.swigReleaseOwnership(); + // mapRenderer.setMapLayerProvider(-1, + // net.osmand.core.jni.OnlineTileSources.getBuiltIn().createProviderFor("Mapnik (OsmAnd)")); + } else { + mapRenderer.resetMapLayerProvider(-1); + } + } + */ + if (!Algorithms.objectEquals(st.MAP_TRANSPARENCY.get(), cachedMapTransparency)) { + cachedMapTransparency = st.MAP_TRANSPARENCY.get(); + MapLayerConfiguration mapLayerConfiguration = new MapLayerConfiguration(); + mapLayerConfiguration.setOpacityFactor(((float) cachedMapTransparency) / 255.0f); + mapRenderer.setMapLayerConfiguration(0, mapLayerConfiguration); + } + /* TODO: Commented to avoid crash (looks like IMapTiledDataProvider.Request parameter does not pass correctly or cannot be resolved while calling obtainImage method) + if (!Algorithms.objectEquals(st.MAP_OVERLAY.get(), cachedOverlay)) { + cachedOverlay = st.MAP_OVERLAY.get(); + ITileSource tileSource = st.getTileSourceByName(cachedOverlay, false); + if (tileSource != null) { + TileSourceProxyProvider prov = new TileSourceProxyProvider(view.getApplication(), tileSource); + mapRenderer.setMapLayerProvider(1, prov.instantiateProxy(true)); + prov.swigReleaseOwnership(); + // mapRenderer.setMapLayerProvider(1, + // net.osmand.core.jni.OnlineTileSources.getBuiltIn().createProviderFor("Mapnik (OsmAnd)")); + } else { + mapRenderer.resetMapLayerProvider(1); + } + } + if (!Algorithms.objectEquals(st.MAP_OVERLAY_TRANSPARENCY.get(), cachedOverlayTransparency)) { + cachedOverlayTransparency = st.MAP_OVERLAY_TRANSPARENCY.get(); + MapLayerConfiguration mapLayerConfiguration = new MapLayerConfiguration(); + mapLayerConfiguration.setOpacityFactor(((float) cachedOverlayTransparency) / 255.0f); + mapRenderer.setMapLayerConfiguration(1, mapLayerConfiguration); + } + */ + // opengl renderer + LatLon ll = tilesRect.getLatLonFromPixel(tilesRect.getPixWidth() / 2, tilesRect.getPixHeight() / 2); + mapRenderer.setTarget(new PointI(MapUtils.get31TileNumberX(ll.getLongitude()), MapUtils.get31TileNumberY(ll + .getLatitude()))); + mapRenderer.setAzimuth(-tilesRect.getRotate()); + mapRenderer.setZoom((float) (tilesRect.getZoom() + tilesRect.getZoomAnimation() + tilesRect + .getZoomFloatPart())); + float zoomMagnifier = st.MAP_DENSITY.get(); + mapRenderer.setVisualZoomShift(zoomMagnifier - 1.0f); + } else { + //if (!view.isZooming()) { + final OsmandMapLayer.DrawSettings drawSettings1 = + new OsmandMapLayer.DrawSettings(drawSettings.isNightMode(), true); + if (resourceManager.updateRenderedMapNeeded(tilesRect, drawSettings1)) { +// pixRect.set(-view.getWidth(), -view.getHeight() / 2, 2 * view.getWidth(), 3 * +// view.getHeight() / 2); + final RotatedTileBox copy = tilesRect.copy(); + copy.increasePixelDimensions(copy.getPixWidth() / 3, copy.getPixHeight() / 4); + resourceManager.updateRendererMap(copy, null); + } + + MapRenderRepositories renderer = resourceManager.getRenderer(); + + RotatedTileBox currentTileBlock = tilesRect; + resourceManager.getRenderer().loadMap(currentTileBlock, resourceManager.getMapTileDownloader()); + drawRenderedMap(canvas, renderer.getBitmap(), renderer.getBitmapLocation(), tilesRect); + drawRenderedMap(canvas, renderer.getPrevBitmap(), renderer.getPrevBmpLocation(), tilesRect); + } + } + + private boolean drawRenderedMap(Canvas canvas, Bitmap bmp, RotatedTileBox bmpLoc, RotatedTileBox currentViewport) { + boolean shown = false; + if (bmp != null && bmpLoc != null) { + float rot = -bmpLoc.getRotate(); + canvas.rotate(rot, currentViewport.getCenterPixelX(), currentViewport.getCenterPixelY()); + final RotatedTileBox calc = currentViewport.copy(); + calc.setRotate(bmpLoc.getRotate()); + QuadPointDouble lt = bmpLoc.getLeftTopTile(bmpLoc.getZoom()); + QuadPointDouble rb = bmpLoc.getRightBottomTile(bmpLoc.getZoom()); + final float x1 = calc.getPixXFromTile(lt.x, lt.y, bmpLoc.getZoom()); + final float x2 = calc.getPixXFromTile(rb.x, rb.y, bmpLoc.getZoom()); + final float y1 = calc.getPixYFromTile(lt.x, lt.y, bmpLoc.getZoom()); + final float y2 = calc.getPixYFromTile(rb.x, rb.y, bmpLoc.getZoom()); + +// LatLon lt = bmpLoc.getLeftTopLatLon(); +// LatLon rb = bmpLoc.getRightBottomLatLon(); +// final float x1 = calc.getPixXFromLatLon(lt.getLatitude(), lt.getLongitude()); +// final float x2 = calc.getPixXFromLatLon(rb.getLatitude(), rb.getLongitude()); +// final float y1 = calc.getPixYFromLatLon(lt.getLatitude(), lt.getLongitude()); +// final float y2 = calc.getPixYFromLatLon(rb.getLatitude(), rb.getLongitude()); + destImage.set(x1, y1, x2, y2); + if (!bmp.isRecycled()) { + canvas.drawBitmap(bmp, null, destImage, paintImg); + shown = true; + } + canvas.rotate(-rot, currentViewport.getCenterPixelX(), currentViewport.getCenterPixelY()); + } + return shown; + } + + @Override + public boolean onLongPressEvent(PointF point, RotatedTileBox tileBox) { + return false; + } + + @Override + public boolean onSingleTap(PointF point, RotatedTileBox tileBox) { + return false; + } +} + diff --git a/OsmAnd/src/net/osmand/plus/server/map/MyCustomLayer.java b/OsmAnd/src/net/osmand/plus/server/map/MyCustomLayer.java new file mode 100644 index 0000000000..a63a51f52f --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/server/map/MyCustomLayer.java @@ -0,0 +1,35 @@ +package net.osmand.plus.server.map; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import net.osmand.data.RotatedTileBox; + +public class MyCustomLayer extends OsmandMapMiniLayer{ + + protected Paint paintBitmap; + + + @Override + public void initLayer(OsmandMapTileMiniView view) { + + paintBitmap = new Paint(); + paintBitmap.setFilterBitmap(true); + paintBitmap.setColor(Color.BLACK); + } + + @Override + public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { + //canvas.drawCircle(canvas.getWidth()/2,canvas.getHeight()/2,40,paintBitmap); + } + + @Override + public void destroyLayer() { + + } + + @Override + public boolean drawInScreenPixels() { + return false; + } +} diff --git a/OsmAnd/src/net/osmand/plus/server/map/OsmandMapMiniLayer.java b/OsmAnd/src/net/osmand/plus/server/map/OsmandMapMiniLayer.java new file mode 100644 index 0000000000..ff01736162 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/server/map/OsmandMapMiniLayer.java @@ -0,0 +1,482 @@ +package net.osmand.plus.server.map; + +import android.content.Context; +import android.graphics.*; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.view.MotionEvent; +import android.widget.ImageView; +import androidx.annotation.NonNull; +import net.osmand.binary.BinaryMapIndexReader; +import net.osmand.data.*; +import net.osmand.osm.PoiCategory; +import net.osmand.plus.ContextMenuAdapter; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.render.OsmandRenderer; +import net.osmand.plus.views.OsmandMapTileView; +import net.osmand.render.RenderingRuleSearchRequest; +import net.osmand.render.RenderingRulesStorage; +import net.osmand.util.MapUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public abstract class OsmandMapMiniLayer { + + protected List fullObjectsLatLon; + protected List smallObjectsLatLon; + + public enum MapGestureType { + DOUBLE_TAP_ZOOM_IN, + DOUBLE_TAP_ZOOM_CHANGE, + TWO_POINTERS_ZOOM_OUT + } + + public boolean isMapGestureAllowed(MapGestureType type) { + return true; + } + + public abstract void initLayer(OsmandMapTileMiniView view); + + public abstract void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings); + + public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { + } + + public abstract void destroyLayer(); + + public void onRetainNonConfigurationInstance(Map map) { + } + + public void populateObjectContextMenu(LatLon latLon, Object o, ContextMenuAdapter adapter, MapActivity mapActivity) { + } + + public boolean onSingleTap(PointF point, RotatedTileBox tileBox) { + return false; + } + + public boolean onLongPressEvent(PointF point, RotatedTileBox tileBox) { + return false; + } + + public boolean onTouchEvent(MotionEvent event, RotatedTileBox tileBox) { + return false; + } + + + public void executeTaskInBackground(AsyncTask task, Params... params) { + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); + } + + public boolean isPresentInFullObjects(LatLon latLon) { + if (fullObjectsLatLon == null) { + return true; + } else if (latLon != null) { + return fullObjectsLatLon.contains(latLon); + } + return false; + } + + public boolean isPresentInSmallObjects(LatLon latLon) { + if (smallObjectsLatLon != null && latLon != null) { + return smallObjectsLatLon.contains(latLon); + } + return false; + } + + /** + * This method returns whether canvas should be rotated as + * map rotated before {@link #onDraw(android.graphics.Canvas, net.osmand.data.RotatedTileBox, DrawSettings)}. + * If the layer draws simply layer over screen (not over map) + * it should return true. + */ + public abstract boolean drawInScreenPixels(); + + public static class DrawSettings { + private final boolean nightMode; + private final boolean updateVectorRendering; + + public DrawSettings(boolean nightMode) { + this(nightMode, false); + } + + public DrawSettings(boolean nightMode, boolean updateVectorRendering) { + this.nightMode = nightMode; + this.updateVectorRendering = updateVectorRendering; + } + + public boolean isUpdateVectorRendering() { + return updateVectorRendering; + } + + public boolean isNightMode() { + return nightMode; + } + } + + @NonNull + public static QuadTree initBoundIntersections(RotatedTileBox tileBox) { + QuadRect bounds = new QuadRect(0, 0, tileBox.getPixWidth(), tileBox.getPixHeight()); + bounds.inset(-bounds.width() / 4, -bounds.height() / 4); + return new QuadTree<>(bounds, 4, 0.6f); + } + + public static boolean intersects(QuadTree boundIntersections, float x, float y, float width, float height) { + List result = new ArrayList<>(); + QuadRect visibleRect = calculateRect(x, y, width, height); + boundIntersections.queryInBox(new QuadRect(visibleRect.left, visibleRect.top, visibleRect.right, visibleRect.bottom), result); + for (QuadRect r : result) { + if (QuadRect.intersects(r, visibleRect)) { + return true; + } + } + boundIntersections.insert(visibleRect, + new QuadRect(visibleRect.left, visibleRect.top, visibleRect.right, visibleRect.bottom)); + return false; + } + + public QuadRect getCorrectedQuadRect(QuadRect latlonRect) { + double topLatitude = latlonRect.top; + double leftLongitude = latlonRect.left; + double bottomLatitude = latlonRect.bottom; + double rightLongitude = latlonRect.right; + // double lat = 0; + // double lon = 0; + // this is buggy lat/lon should be 0 but in that case + // it needs to be fixed in case there is no route points in the view bbox + double lat = topLatitude - bottomLatitude + 0.1; + double lon = rightLongitude - leftLongitude + 0.1; + return new QuadRect(leftLongitude - lon, topLatitude + lat, rightLongitude + lon, bottomLatitude - lat); + } + + public static QuadRect calculateRect(float x, float y, float width, float height) { + QuadRect rf; + double left = x - width / 2.0d; + double top = y - height / 2.0d; + double right = left + width; + double bottom = top + height; + rf = new QuadRect(left, top, right, bottom); + return rf; + } + + public Amenity findAmenity(OsmandApplication app, long id, List names, LatLon latLon, int radius) { + QuadRect rect = MapUtils.calculateLatLonBbox(latLon.getLatitude(), latLon.getLongitude(), radius); + List amenities = app.getResourceManager().searchAmenities( + new BinaryMapIndexReader.SearchPoiTypeFilter() { + @Override + public boolean accept(PoiCategory type, String subcategory) { + return true; + } + + @Override + public boolean isEmpty() { + return false; + } + }, rect.top, rect.left, rect.bottom, rect.right, -1, null); + + Amenity res = null; + for (Amenity amenity : amenities) { + Long amenityId = amenity.getId() >> 1; + if (amenityId == id && !amenity.isClosed()) { + res = amenity; + break; + } + } + if (res == null && names != null && names.size() > 0) { + for (Amenity amenity : amenities) { + for (String name : names) { + if (name.equals(amenity.getName()) && !amenity.isClosed()) { + res = amenity; + break; + } + } + if (res != null) { + break; + } + } + } + + return res; + } + + public int getDefaultRadiusPoi(RotatedTileBox tb) { + int r; + final double zoom = tb.getZoom(); + if (zoom <= 15) { + r = 10; + } else if (zoom <= 16) { + r = 14; + } else if (zoom <= 17) { + r = 16; + } else { + r = 18; + } + return (int) (r * tb.getDensity()); + } + + protected int getIconSize(Context ctx) { + return ctx.getResources().getDimensionPixelSize(R.dimen.favorites_icon_outline_size); + } + + public Rect getIconDestinationRect(float x, float y, int width, int height, float scale) { + int scaledWidth = width; + int scaledHeight = height; + if (scale != 1.0f) { + scaledWidth = (int) (width * scale); + scaledHeight = (int) (height * scale); + } + Rect rect = new Rect(0, 0, scaledWidth, scaledHeight); + rect.offset((int) x - scaledWidth / 2, (int) y - scaledHeight / 2); + return rect; + } + + public int getScaledTouchRadius(OsmandApplication app, int radiusPoi) { + float textScale = app.getSettings().TEXT_SCALE.get(); + if (textScale < 1.0f) { + textScale = 1.0f; + } + return (int) textScale * radiusPoi; + } + + public void setMapButtonIcon(ImageView imageView, Drawable icon) { + int btnSizePx = imageView.getLayoutParams().height; + int iconSizePx = imageView.getContext().getResources().getDimensionPixelSize(R.dimen.map_widget_icon); + int iconPadding = (btnSizePx - iconSizePx) / 2; + imageView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding); + imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); + imageView.setImageDrawable(icon); + } + + public abstract class MapLayerData { + public int ZOOM_THRESHOLD = 1; + public RotatedTileBox queriedBox; + protected T results; + protected Task currentTask; + protected Task pendingTask; + + public RotatedTileBox getQueriedBox() { + return queriedBox; + } + + public T getResults() { + return results; + } + + public boolean queriedBoxContains(final RotatedTileBox queriedData, final RotatedTileBox newBox) { + return queriedData != null && queriedData.containsTileBox(newBox) && Math.abs(queriedData.getZoom() - newBox.getZoom()) <= ZOOM_THRESHOLD; + } + + public void queryNewData(RotatedTileBox tileBox) { + if (!queriedBoxContains(queriedBox, tileBox)) { + Task ct = currentTask; + if (ct == null || !queriedBoxContains(ct.getDataBox(), tileBox)) { + RotatedTileBox original = tileBox.copy(); + RotatedTileBox extended = original.copy(); + extended.increasePixelDimensions(tileBox.getPixWidth() / 2, tileBox.getPixHeight() / 2); + Task task = new Task(original, extended); + if (currentTask == null) { + executeTaskInBackground(task); + } else { + pendingTask = task; + } + } + + } + } + + public void layerOnPreExecute() { + } + + public void layerOnPostExecute() { + } + + public boolean isInterrupted() { + return pendingTask != null; + } + + protected abstract T calculateResult(RotatedTileBox tileBox); + + public class Task extends AsyncTask { + private RotatedTileBox dataBox; + private RotatedTileBox requestedBox; + + public Task(RotatedTileBox requestedBox, RotatedTileBox dataBox) { + this.requestedBox = requestedBox; + this.dataBox = dataBox; + } + + public RotatedTileBox getOriginalBox() { + return requestedBox; + } + + public RotatedTileBox getDataBox() { + return dataBox; + } + + @Override + protected T doInBackground(Object... params) { + if (queriedBoxContains(queriedBox, dataBox)) { + return null; + } + return calculateResult(dataBox); + } + + @Override + protected void onPreExecute() { + currentTask = this; + layerOnPreExecute(); + } + + @Override + protected void onPostExecute(T result) { + if (result != null) { + queriedBox = dataBox; + results = result; + } + currentTask = null; + if (pendingTask != null) { + executeTaskInBackground(pendingTask); + pendingTask = null; + } else { + layerOnPostExecute(); + } + } + } + + public void clearCache() { + results = null; + queriedBox = null; + + } + + } + + public static class RenderingLineAttributes { + protected int cachedHash; + public Paint paint; + public Paint customColorPaint; + public int customColor = 0; + public int defaultWidth = 0; + public int defaultColor = 0; + public boolean isPaint2; + public Paint paint2; + public int defaultWidth2 = 0; + public boolean isPaint3; + public Paint paint3; + public int defaultWidth3 = 0; + public Paint shadowPaint; + public boolean isShadowPaint; + public int defaultShadowWidthExtent = 2; + public Paint paint_1; + public boolean isPaint_1; + public int defaultWidth_1 = 0; + private String renderingAttribute; + + public RenderingLineAttributes(String renderingAttribute) { + this.renderingAttribute = renderingAttribute; + paint = initPaint(); + customColorPaint = new Paint(paint); + paint2 = initPaint(); + paint3 = initPaint(); + paint_1 = initPaint(); + shadowPaint = initPaint(); + } + + + private Paint initPaint() { + Paint paint = new Paint(); + paint.setStyle(Paint.Style.STROKE); + paint.setAntiAlias(true); + paint.setStrokeCap(Paint.Cap.ROUND); + paint.setStrokeJoin(Paint.Join.ROUND); + return paint; + } + + + public boolean updatePaints(OsmandApplication app, DrawSettings settings, RotatedTileBox tileBox) { + OsmandRenderer renderer = app.getResourceManager().getRenderer().getRenderer(); + RenderingRulesStorage rrs = app.getRendererRegistry().getCurrentSelectedRenderer(); + final boolean isNight = settings != null && settings.isNightMode(); + int hsh = calculateHash(rrs, isNight, tileBox.getDensity()); + if (hsh != cachedHash) { + cachedHash = hsh; + if (rrs != null) { + RenderingRuleSearchRequest req = new RenderingRuleSearchRequest(rrs); + req.setBooleanFilter(rrs.PROPS.R_NIGHT_MODE, isNight); + if (req.searchRenderingAttribute(renderingAttribute)) { + OsmandRenderer.RenderingContext rc = new OsmandRenderer.RenderingContext(app); + rc.setDensityValue((float) tileBox.getDensity()); + // cachedColor = req.getIntPropertyValue(rrs.PROPS.R_COLOR); + renderer.updatePaint(req, paint, 0, false, rc); + isPaint2 = renderer.updatePaint(req, paint2, 1, false, rc); + if (paint2.getStrokeWidth() == 0 && defaultWidth2 != 0) { + paint2.setStrokeWidth(defaultWidth2); + } + isPaint3 = renderer.updatePaint(req, paint3, 2, false, rc); + if (paint3.getStrokeWidth() == 0 && defaultWidth3 != 0) { + paint3.setStrokeWidth(defaultWidth3); + } + isPaint_1 = renderer.updatePaint(req, paint_1, -1, false, rc); + if (paint_1.getStrokeWidth() == 0 && defaultWidth_1 != 0) { + paint_1.setStrokeWidth(defaultWidth_1); + } + isShadowPaint = req.isSpecified(rrs.PROPS.R_SHADOW_RADIUS); + if (isShadowPaint) { + ColorFilter cf = new PorterDuffColorFilter( + req.getIntPropertyValue(rrs.PROPS.R_SHADOW_COLOR), PorterDuff.Mode.SRC_IN); + shadowPaint.setColorFilter(cf); + shadowPaint.setStrokeWidth(paint.getStrokeWidth() + defaultShadowWidthExtent + * rc.getComplexValue(req, rrs.PROPS.R_SHADOW_RADIUS)); + } + } else { + System.err.println("Rendering attribute route is not found !"); + } + updateDefaultColor(paint, defaultColor); + if (paint.getStrokeWidth() == 0 && defaultWidth != 0) { + paint.setStrokeWidth(defaultWidth); + } + customColorPaint = new Paint(paint); + } + return true; + } + return false; + } + + + private void updateDefaultColor(Paint paint, int defaultColor) { + if ((paint.getColor() == 0 || paint.getColor() == Color.BLACK) && defaultColor != 0) { + paint.setColor(defaultColor); + } + } + + private int calculateHash(Object... o) { + return Arrays.hashCode(o); + } + + public void drawPath(Canvas canvas, Path path) { + if (isPaint_1) { + canvas.drawPath(path, paint_1); + } + if (isShadowPaint) { + canvas.drawPath(path, shadowPaint); + } + if (customColor != 0) { + customColorPaint.setColor(customColor); + canvas.drawPath(path, customColorPaint); + } else { + canvas.drawPath(path, paint); + } + if (isPaint2) { + canvas.drawPath(path, paint2); + } + if (isPaint3) { + canvas.drawPath(path, paint3); + } + } + } +} + diff --git a/OsmAnd/src/net/osmand/plus/server/map/OsmandMapTileMiniView.java b/OsmAnd/src/net/osmand/plus/server/map/OsmandMapTileMiniView.java new file mode 100644 index 0000000000..785c2a9961 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/server/map/OsmandMapTileMiniView.java @@ -0,0 +1,1314 @@ +package net.osmand.plus.server.map; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.*; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.util.DisplayMetrics; +import android.view.*; +import android.widget.Toast; +import net.osmand.AndroidUtils; +import net.osmand.PlatformUtil; +import net.osmand.access.AccessibilityActionsProvider; +import net.osmand.core.android.MapRendererView; +import net.osmand.data.LatLon; +import net.osmand.data.QuadPoint; +import net.osmand.data.QuadPointDouble; +import net.osmand.data.RotatedTileBox; +import net.osmand.map.IMapLocationListener; +import net.osmand.map.MapTileDownloader; +import net.osmand.plus.OsmAndConstants; +import net.osmand.plus.OsmAndFormatter; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.helpers.TwoFingerTapDetector; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.views.*; +import net.osmand.plus.views.layers.ContextMenuLayer; +import net.osmand.render.RenderingRuleSearchRequest; +import net.osmand.render.RenderingRuleStorageProperties; +import net.osmand.render.RenderingRulesStorage; +import net.osmand.util.MapUtils; +import org.apache.commons.logging.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public class OsmandMapTileMiniView implements MapTileDownloader.IMapDownloaderCallback { + private static final int MAP_REFRESH_MESSAGE = OsmAndConstants.UI_HANDLER_MAP_VIEW + 4; + private static final int MAP_FORCE_REFRESH_MESSAGE = OsmAndConstants.UI_HANDLER_MAP_VIEW + 5; + private static final int BASE_REFRESH_MESSAGE = OsmAndConstants.UI_HANDLER_MAP_VIEW + 3; + protected final static int LOWEST_ZOOM_TO_ROTATE = 9; + private static final int MAP_DEFAULT_COLOR = 0xffebe7e4; + private boolean MEASURE_FPS = false; + private FPSMeasurement main = new FPSMeasurement(); + private FPSMeasurement additional = new FPSMeasurement(); + private View view; + private OsmandApplication application; + protected OsmandSettings settings = null; + private CanvasColors canvasColors = null; + private Boolean nightMode = null; + public Bitmap currentCanvas = null; + + private class CanvasColors { + int colorDay = MAP_DEFAULT_COLOR; + int colorNight = MAP_DEFAULT_COLOR; + } + + private class FPSMeasurement { + int fpsMeasureCount = 0; + int fpsMeasureMs = 0; + long fpsFirstMeasurement = 0; + float fps; + + void calculateFPS(long start, long end) { + fpsMeasureMs += end - start; + fpsMeasureCount++; + if (fpsMeasureCount > 10 || (start - fpsFirstMeasurement) > 400) { + fpsFirstMeasurement = start; + fps = (1000f * fpsMeasureCount / fpsMeasureMs); + fpsMeasureCount = 0; + fpsMeasureMs = 0; + } + } + } + + + protected static final int emptyTileDivisor = 16; + + + public interface OnTrackBallListener { + public boolean onTrackBallEvent(MotionEvent e); + } + + public interface OnLongClickListener { + public boolean onLongPressEvent(PointF point); + } + + public interface OnClickListener { + public boolean onPressEvent(PointF point); + } + + public interface OnDrawMapListener { + public void onDrawOverMap(); + } + + protected static final Log LOG = PlatformUtil.getLog(net.osmand.plus.views.OsmandMapTileView.class); + + + private RotatedTileBox currentViewport; + + private float rotate; // accumulate + + private int mapPosition; + private int mapPositionX; + + private float mapRatioX; + private float mapRatioY; + + private boolean showMapPosition = true; + + private IMapLocationListener locationListener; + + private net.osmand.plus.views.OsmandMapTileView.OnLongClickListener onLongClickListener; + + private net.osmand.plus.views.OsmandMapTileView.OnClickListener onClickListener; + + private net.osmand.plus.views.OsmandMapTileView.OnTrackBallListener trackBallDelegate; + + private AccessibilityActionsProvider accessibilityActions; + + private List layers = new ArrayList<>(); + + private BaseMapLayer mainLayer; + + private Map zOrders = new HashMap(); + + private net.osmand.plus.views.OsmandMapTileView.OnDrawMapListener onDrawMapListener; + + // UI Part + // handler to refresh map (in ui thread - ui thread is not necessary, but msg queue is required). + protected Handler handler; + private Handler baseHandler; + + private AnimateDraggingMapThread animatedDraggingThread; + + Paint paintGrayFill; + Paint paintBlackFill; + Paint paintWhiteFill; + Paint paintCenter; + + private DisplayMetrics dm; + private MapRendererView mapRenderer; + + private Bitmap bufferBitmap; + private RotatedTileBox bufferImgLoc; + private Bitmap bufferBitmapTmp; + + private Paint paintImg; + + private GestureDetector gestureDetector; + private MultiTouchSupport multiTouchSupport; + private DoubleTapScaleDetector doubleTapScaleDetector; + private TwoFingerTapDetector twoFingersTapDetector; + //private boolean afterTwoFingersTap = false; + private boolean afterDoubleTap = false; + private boolean wasMapLinkedBeforeGesture = false; + + private LatLon firstTouchPointLatLon; + private LatLon secondTouchPointLatLon; + private boolean multiTouch; + private long multiTouchStartTime; + private long multiTouchEndTime; + private boolean wasZoomInMultiTouch; + private float elevationAngle; + + public OsmandMapTileMiniView(OsmandApplication app, int w, int h) { + this.application = app; + init(w, h); + } + + // ///////////////////////////// INITIALIZING UI PART /////////////////////////////////// + public void init(int w, int h) { + settings = application.getSettings(); + + paintGrayFill = new Paint(); + paintGrayFill.setColor(Color.GRAY); + paintGrayFill.setStyle(Paint.Style.FILL); + // when map rotate + paintGrayFill.setAntiAlias(true); + + paintBlackFill = new Paint(); + paintBlackFill.setColor(Color.BLACK); + paintBlackFill.setStyle(Paint.Style.FILL); + // when map rotate + paintBlackFill.setAntiAlias(true); + + paintWhiteFill = new Paint(); + paintWhiteFill.setColor(Color.WHITE); + paintWhiteFill.setStyle(Paint.Style.FILL); + // when map rotate + paintWhiteFill.setAntiAlias(true); + + paintCenter = new Paint(); + paintCenter.setStyle(Paint.Style.STROKE); + paintCenter.setColor(Color.rgb(60, 60, 60)); + paintCenter.setStrokeWidth(2); + paintCenter.setAntiAlias(true); + + paintImg = new Paint(); + paintImg.setFilterBitmap(true); +// paintImg.setDither(true); + + //handler = new Handler(); + //baseHandler = new Handler(application.getResourceManager().getRenderingBufferImageThread().getLooper()); + + WindowManager mgr = (WindowManager) application.getSystemService(Context.WINDOW_SERVICE); + dm = new DisplayMetrics(); + mgr.getDefaultDisplay().getMetrics(dm); + LatLon ll = settings.getLastKnownMapLocation(); + currentViewport = new RotatedTileBox.RotatedTileBoxBuilder(). + setLocation(ll.getLatitude(), ll.getLongitude()).setZoom(settings.getLastKnownMapZoom()). + setPixelDimensions(w, h).build(); + currentViewport.setDensity(dm.density); + currentViewport.setMapDensity(getSettingsMapDensity()); + + elevationAngle = settings.getLastKnownMapElevation(); + } + + public void setView(View view) { + this.view = view; + view.setClickable(true); + view.setLongClickable(true); + view.setFocusable(true); + if (Build.VERSION.SDK_INT >= 26) { + view.setDefaultFocusHighlightEnabled(false); + } + refreshMap(true); + } + + public Boolean onKeyDown(int keyCode, KeyEvent event) { + return application.accessibilityEnabled() ? false : null; + } + + public boolean isLayerVisible(OsmandMapMiniLayer layer) { + return layers.contains(layer); + } + + public float getZorder(OsmandMapMiniLayer layer) { + Float z = zOrders.get(layer); + if (z == null) { + return 10; + } + return z; + } + + public synchronized void addLayer(OsmandMapMiniLayer layer, float zOrder) { + int i = 0; + for (i = 0; i < layers.size(); i++) { + if (zOrders.get(layers.get(i)) > zOrder) { + break; + } + } + layer.initLayer(this); + layers.add(i, layer); + zOrders.put(layer, zOrder); + } + + public synchronized void removeLayer(OsmandMapMiniLayer layer) { + while (layers.remove(layer)) ; + zOrders.remove(layer); + layer.destroyLayer(); + } + + public synchronized void removeAllLayers() { + while (layers.size() > 0) { + removeLayer(layers.get(0)); + } + } + + public List getLayers() { + return layers; + } + + @SuppressWarnings("unchecked") + public T getLayerByClass(Class cl) { + for (OsmandMapMiniLayer lr : layers) { + if (cl.isInstance(lr)) { + return (T) lr; + } + } + return null; + } + + public int getViewHeight() { + if (view != null) { + return view.getHeight(); + } else { + return 0; + } + } + + public OsmandApplication getApplication() { + return application; + } + + // ///////////////////////// NON UI PART (could be extracted in common) ///////////////////////////// + public LatLon getFirstTouchPointLatLon() { + return firstTouchPointLatLon; + } + + public LatLon getSecondTouchPointLatLon() { + return secondTouchPointLatLon; + } + + public boolean isMultiTouch() { + return multiTouch; + } + + public long getMultiTouchStartTime() { + return multiTouchStartTime; + } + + public long getMultiTouchEndTime() { + return multiTouchEndTime; + } + + public boolean isWasZoomInMultiTouch() { + return wasZoomInMultiTouch; + } + + public boolean mapGestureAllowed(OsmandMapMiniLayer.MapGestureType type) { +// for (OsmandMapMiniLayer layer : layers) { +// if (!layer.isMapGestureAllowed(type)) { +// return false; +// } +// } + return true; + } + + public void setIntZoom(int zoom) { + zoom = zoom < getMinZoom() ? getMinZoom() : zoom; + if (mainLayer != null) { + animatedDraggingThread.stopAnimating(); + currentViewport.setZoomAndAnimation(zoom, 0, 0); + if (zoom <= LOWEST_ZOOM_TO_ROTATE) { + rotate = 0; + } + currentViewport.setRotate(rotate); + refreshMap(); + } + } + + public void setComplexZoom(int zoom, double mapDensity) { + if (mainLayer != null && zoom <= getMaxZoom() && zoom >= getMinZoom()) { + animatedDraggingThread.stopAnimating(); + currentViewport.setZoomAndAnimation(zoom, 0); + currentViewport.setMapDensity(mapDensity); + if (zoom <= LOWEST_ZOOM_TO_ROTATE) { + rotate = 0; + } + currentViewport.setRotate(rotate); + refreshMap(); + } + } + + + public boolean isMapRotateEnabled() { + return getZoom() > LOWEST_ZOOM_TO_ROTATE; + } + + public void setRotate(float rotate, boolean force) { + if (isMapRotateEnabled()) { + float diff = MapUtils.unifyRotationDiff(rotate, getRotate()); + if (Math.abs(diff) > 5 || force) { // check smallest rotation + animatedDraggingThread.startRotate(rotate); + } + } + } + + public boolean isShowMapPosition() { + return showMapPosition; + } + + public void setShowMapPosition(boolean showMapPosition) { + this.showMapPosition = showMapPosition; + } + + public float getRotate() { + return currentViewport.getRotate(); + } + + + public void setLatLon(double latitude, double longitude) { + animatedDraggingThread.stopAnimating(); + currentViewport.setLatLonCenter(latitude, longitude); + refreshMap(); + } + + public double getLatitude() { + return currentViewport.getLatitude(); + } + + public double getLongitude() { + return currentViewport.getLongitude(); + } + + public int getZoom() { + return currentViewport.getZoom(); + } + + public float getElevationAngle() { + return elevationAngle; + } + + public double getZoomFractionalPart() { + return currentViewport.getZoomFloatPart(); + } + + public double getSettingsMapDensity() { + return (getSettings().MAP_DENSITY.get()) * Math.max(1, getDensity()); + } + + + public boolean isZooming() { + return currentViewport.isZoomAnimated(); + } + + public void setMapLocationListener(IMapLocationListener l) { + locationListener = l; + } + + /** + * Adds listener to control when map is dragging + */ + public IMapLocationListener setMapLocationListener() { + return locationListener; + } + + public void setOnDrawMapListener(net.osmand.plus.views.OsmandMapTileView.OnDrawMapListener onDrawMapListener) { + this.onDrawMapListener = onDrawMapListener; + } + + // ////////////////////////////// DRAWING MAP PART ///////////////////////////////////////////// + public BaseMapLayer getMainLayer() { + return mainLayer; + } + + public void setMainLayer(BaseMapLayer mainLayer) { + this.mainLayer = mainLayer; + int zoom = currentViewport.getZoom(); + + if (getMaxZoom() < zoom) { + zoom = getMaxZoom(); + } + if (getMinZoom() > zoom) { + zoom = getMinZoom(); + } + currentViewport.setZoomAndAnimation(zoom, 0, 0); + refreshMap(); + } + + public int getMapPosition() { + return mapPosition; + } + + public void setMapPosition(int type) { + this.mapPosition = type; + } + + public void setMapPositionX(int type) { + this.mapPositionX = type; + } + + public void setCustomMapRatio(float ratioX, float ratioY) { + this.mapRatioX = ratioX; + this.mapRatioY = ratioY; + } + + public void restoreMapRatio() { + RotatedTileBox box = currentViewport.copy(); + float rx = (float) box.getCenterPixelX() / box.getPixWidth(); + float ry = (float) box.getCenterPixelY() / box.getPixHeight(); + if (mapPosition == OsmandSettings.BOTTOM_CONSTANT) { + ry -= 0.35; + } + box.setCenterLocation(rx, ry); + LatLon screenCenter = box.getLatLonFromPixel(box.getPixWidth() / 2f, box.getPixHeight() / 2f); + mapRatioX = 0; + mapRatioY = 0; + setLatLon(screenCenter.getLatitude(), screenCenter.getLongitude()); + } + + public boolean hasCustomMapRatio() { + return mapRatioX != 0 && mapRatioY != 0; + } + + public OsmandSettings getSettings() { + return settings; + } + + public int getMaxZoom() { + if (mainLayer != null) { + return mainLayer.getMaximumShownMapZoom(); + } + return BaseMapLayer.DEFAULT_MAX_ZOOM; + } + + public int getMinZoom() { + if (mainLayer != null) { + return mainLayer.getMinimumShownMapZoom() + 1; + } + return BaseMapLayer.DEFAULT_MIN_ZOOM; + } + + private void drawBasemap(Canvas canvas) { + if (bufferImgLoc != null) { + float rot = -bufferImgLoc.getRotate(); + canvas.rotate(rot, currentViewport.getCenterPixelX(), currentViewport.getCenterPixelY()); + final RotatedTileBox calc = currentViewport.copy(); + calc.setRotate(bufferImgLoc.getRotate()); + + int cz = getZoom(); + QuadPointDouble lt = bufferImgLoc.getLeftTopTile(cz); + QuadPointDouble rb = bufferImgLoc.getRightBottomTile(cz); + final float x1 = calc.getPixXFromTile(lt.x, lt.y, cz); + final float x2 = calc.getPixXFromTile(rb.x, rb.y, cz); + final float y1 = calc.getPixYFromTile(lt.x, lt.y, cz); + final float y2 = calc.getPixYFromTile(rb.x, rb.y, cz); +// LatLon lt = bufferImgLoc.getLeftTopLatLon(); +// LatLon rb = bufferImgLoc.getRightBottomLatLon(); +// final float x1 = calc.getPixXFromLatLon(lt.getLatitude(), lt.getLongitude()); +// final float x2 = calc.getPixXFromLatLon(rb.getLatitude(), rb.getLongitude()); +// final float y1 = calc.getPixYFromLatLon(lt.getLatitude(), lt.getLongitude()); +// final float y2 = calc.getPixYFromLatLon(rb.getLatitude(), rb.getLongitude()); + if (!bufferBitmap.isRecycled()) { + RectF rct = new RectF(x1, y1, x2, y2); + canvas.drawBitmap(bufferBitmap, null, rct, paintImg); + currentCanvas = bufferBitmap; + } + canvas.rotate(-rot, currentViewport.getCenterPixelX(), currentViewport.getCenterPixelY()); + } + } + + private void refreshBaseMapInternal(RotatedTileBox tileBox, OsmandMapMiniLayer.DrawSettings drawSettings) { + if (tileBox.getPixHeight() == 0 || tileBox.getPixWidth() == 0) { + return; + } + if (bufferBitmapTmp == null || tileBox.getPixHeight() != bufferBitmapTmp.getHeight() + || tileBox.getPixWidth() != bufferBitmapTmp.getWidth()) { + bufferBitmapTmp = Bitmap.createBitmap(tileBox.getPixWidth(), tileBox.getPixHeight(), Bitmap.Config.ARGB_8888); + } + long start = SystemClock.elapsedRealtime(); + final QuadPoint c = tileBox.getCenterPixelPoint(); + Canvas canvas = new Canvas(bufferBitmapTmp); + fillCanvas(canvas, drawSettings); + for (int i = 0; i < layers.size(); i++) { + try { + OsmandMapMiniLayer layer = layers.get(i); + canvas.save(); + // rotate if needed + if (!layer.drawInScreenPixels()) { + canvas.rotate(tileBox.getRotate(), c.x, c.y); + } + layer.onPrepareBufferImage(canvas, tileBox, drawSettings); + canvas.restore(); + } catch (IndexOutOfBoundsException e) { + // skip it + canvas.restore(); + } + } + Bitmap t = bufferBitmap; + synchronized (this) { + bufferImgLoc = tileBox; + bufferBitmap = bufferBitmapTmp; + bufferBitmapTmp = t; + currentCanvas = bufferBitmap; + } + long end = SystemClock.elapsedRealtime(); + additional.calculateFPS(start, end); + } + + public void refreshMapInternal(OsmandMapMiniLayer.DrawSettings drawSettings) { + if (view == null) { + return; + } + final float ratioy; + if (mapRatioY != 0) { + ratioy = mapRatioY; + } else if (mapPosition == OsmandSettings.BOTTOM_CONSTANT) { + ratioy = 0.85f; + } else if (mapPosition == OsmandSettings.MIDDLE_BOTTOM_CONSTANT) { + ratioy = 0.70f; + } else if (mapPosition == OsmandSettings.MIDDLE_TOP_CONSTANT) { + ratioy = 0.25f; + } else { + ratioy = 0.5f; + } + final float ratiox; + if (mapRatioX != 0) { + ratiox = mapRatioX; + } else if (mapPosition == OsmandSettings.LANDSCAPE_MIDDLE_RIGHT_CONSTANT) { + ratiox = 0.7f; + } else { + boolean isLayoutRtl = AndroidUtils.isLayoutRtl(application); + ratiox = mapPositionX == 0 ? 0.5f : (isLayoutRtl ? 0.25f : 0.75f); + } + final int cy = (int) (ratioy * view.getHeight()); + final int cx = (int) (ratiox * view.getWidth()); + if (currentViewport.getPixWidth() != view.getWidth() || currentViewport.getPixHeight() != view.getHeight() || + currentViewport.getCenterPixelY() != cy || + currentViewport.getCenterPixelX() != cx) { + currentViewport.setPixelDimensions(view.getWidth(), view.getHeight(), ratiox, ratioy); + refreshBufferImage(drawSettings); + } + if (view instanceof SurfaceView) { + SurfaceHolder holder = ((SurfaceView) view).getHolder(); + long ms = SystemClock.elapsedRealtime(); + synchronized (holder) { + Canvas canvas = holder.lockCanvas(); + if (canvas != null) { + try { + // make copy to avoid concurrency + RotatedTileBox viewportToDraw = currentViewport.copy(); + drawOverMap(canvas, viewportToDraw, drawSettings); + } finally { + holder.unlockCanvasAndPost(canvas); + } + } + if (MEASURE_FPS) { + main.calculateFPS(ms, SystemClock.elapsedRealtime()); + } + } + } else { + view.invalidate(); + } + } + + private void fillCanvas(Canvas canvas, OsmandMapMiniLayer.DrawSettings drawSettings) { + int color = MAP_DEFAULT_COLOR; + CanvasColors canvasColors = this.canvasColors; + if (canvasColors == null) { + canvasColors = updateCanvasColors(); + this.canvasColors = canvasColors; + } + if (canvasColors != null) { + color = drawSettings.isNightMode() ? canvasColors.colorNight : canvasColors.colorDay; + } + canvas.drawColor(color); + } + + public void resetDefaultColor() { + canvasColors = null; + } + + private CanvasColors updateCanvasColors() { + CanvasColors canvasColors = null; + RenderingRulesStorage rrs = application.getRendererRegistry().getCurrentSelectedRenderer(); + if (rrs != null) { + canvasColors = new CanvasColors(); + RenderingRuleSearchRequest req = new RenderingRuleSearchRequest(rrs); + req.setBooleanFilter(rrs.PROPS.R_NIGHT_MODE, false); + if (req.searchRenderingAttribute(RenderingRuleStorageProperties.A_DEFAULT_COLOR)) { + canvasColors.colorDay = req.getIntPropertyValue(req.ALL.R_ATTR_COLOR_VALUE); + } + req = new RenderingRuleSearchRequest(rrs); + req.setBooleanFilter(rrs.PROPS.R_NIGHT_MODE, true); + if (req.searchRenderingAttribute(RenderingRuleStorageProperties.A_DEFAULT_COLOR)) { + canvasColors.colorNight = req.getIntPropertyValue(req.ALL.R_ATTR_COLOR_VALUE); + } + } + return canvasColors; + } + + public boolean isMeasureFPS() { + return MEASURE_FPS; + } + + public void setMeasureFPS(boolean measureFPS) { + MEASURE_FPS = measureFPS; + } + + public float getFPS() { + return main.fps; + } + + public float getSecondaryFPS() { + return additional.fps; + } + + public boolean isAnimatingZoom() { + return animatedDraggingThread.isAnimatingZoom(); + } + + @SuppressLint("WrongCall") + public void drawOverMap(Canvas canvas, RotatedTileBox tileBox, OsmandMapMiniLayer.DrawSettings drawSettings) { + if (mapRenderer == null) { + fillCanvas(canvas, drawSettings); + } + final QuadPoint c = tileBox.getCenterPixelPoint(); + synchronized (this) { + //if (bufferBitmap != null && !bufferBitmap.isRecycled() && mapRenderer == null) { + //canvas.save(); + //canvas.rotate(tileBox.getRotate(), c.x, c.y); + drawBasemap(canvas); + //canvas.restore(); + //} + } + + if (onDrawMapListener != null) { + onDrawMapListener.onDrawOverMap(); + } + + for (int i = 0; i < layers.size(); i++) { + try { + OsmandMapMiniLayer layer = layers.get(i); + canvas.save(); + // rotate if needed + if (!layer.drawInScreenPixels()) { + canvas.rotate(tileBox.getRotate(), c.x, c.y); + } + if (mapRenderer != null) { + layer.onPrepareBufferImage(canvas, tileBox, drawSettings); + } + try{ + layer.onDraw(canvas, tileBox, drawSettings); + } + catch (Exception e){ + e.printStackTrace(); + } + canvas.restore(); + } catch (IndexOutOfBoundsException e) { + // skip it + } + } + if (showMapPosition || animatedDraggingThread.isAnimatingZoom()) { + drawMapPosition(canvas, c.x, c.y); + } else if (multiTouchSupport.isInZoomMode()) { + drawMapPosition(canvas, multiTouchSupport.getCenterPoint().x, multiTouchSupport.getCenterPoint().y); + } else if (doubleTapScaleDetector.isInZoomMode()) { + drawMapPosition(canvas, doubleTapScaleDetector.getCenterX(), doubleTapScaleDetector.getCenterY()); + } + } + + + protected void drawMapPosition(Canvas canvas, float x, float y) { + canvas.drawCircle(x, y, 3 * dm.density, paintCenter); + canvas.drawCircle(x, y, 7 * dm.density, paintCenter); + } + + private void refreshBufferImage(final OsmandMapMiniLayer.DrawSettings drawSettings) { + if (mapRenderer != null) { + return; + } + + OsmandMapMiniLayer.DrawSettings param = drawSettings; + Boolean currentNightMode = nightMode; + param = new OsmandMapMiniLayer.DrawSettings(currentNightMode, true); + refreshBaseMapInternal(currentViewport.copy(), param); + return; + +// if (!baseHandler.hasMessages(BASE_REFRESH_MESSAGE) || drawSettings.isUpdateVectorRendering()) { +// Message msg = Message.obtain(baseHandler, new Runnable() { +// @Override +// public void run() { +// baseHandler.removeMessages(BASE_REFRESH_MESSAGE); +// try { +// OsmandMapMiniLayer.DrawSettings param = drawSettings; +// Boolean currentNightMode = nightMode; +// if (currentNightMode != null && currentNightMode != param.isNightMode()) { +// param = new OsmandMapMiniLayer.DrawSettings(currentNightMode, true); +// resetDefaultColor(); +// } +// if (handler.hasMessages(MAP_FORCE_REFRESH_MESSAGE)) { +// if (!param.isUpdateVectorRendering()) { +// param = new OsmandMapMiniLayer.DrawSettings(drawSettings.isNightMode(), true); +// } +// handler.removeMessages(MAP_FORCE_REFRESH_MESSAGE); +// } +// refreshBaseMapInternal(currentViewport.copy(), param); +// //sendRefreshMapMsg(param, 0); +// } catch (Exception e) { +// LOG.error(e.getMessage(), e); +// } +// } +// }); +// msg.what = drawSettings.isUpdateVectorRendering() ? MAP_FORCE_REFRESH_MESSAGE : BASE_REFRESH_MESSAGE; +// // baseHandler.sendMessageDelayed(msg, 0); +// baseHandler.sendMessage(msg); +// } + } + + // this method could be called in non UI thread + public void refreshMap() { + refreshMap(false); + } + + // this method could be called in non UI thread + public void refreshMap(final boolean updateVectorRendering) { + + boolean nightMode = application.getDaynightHelper().isNightMode(); + Boolean currentNightMode = this.nightMode; + boolean forceUpdateVectorDrawing = currentNightMode != null && currentNightMode != nightMode; + if (forceUpdateVectorDrawing) { + resetDefaultColor(); + } + this.nightMode = nightMode; + OsmandMapMiniLayer.DrawSettings drawSettings = new OsmandMapMiniLayer.DrawSettings(nightMode, updateVectorRendering || forceUpdateVectorDrawing); + //sendRefreshMapMsg(drawSettings, 20); + refreshBufferImage(drawSettings); + } + + private void sendRefreshMapMsg(final OsmandMapMiniLayer.DrawSettings drawSettings, int delay) { + if (!handler.hasMessages(MAP_REFRESH_MESSAGE) || drawSettings.isUpdateVectorRendering()) { + Message msg = Message.obtain(handler, new Runnable() { + @Override + public void run() { + OsmandMapMiniLayer.DrawSettings param = drawSettings; + handler.removeMessages(MAP_REFRESH_MESSAGE); + + refreshMapInternal(param); + } + }); + msg.what = MAP_REFRESH_MESSAGE; + if (delay > 0) { + handler.sendMessageDelayed(msg, delay); + } else { + handler.sendMessage(msg); + } + } + } + + @Override + public void tileDownloaded(MapTileDownloader.DownloadRequest request) { + // force to refresh map because image can be loaded from different threads + // and threads can block each other especially for sqlite images when they + // are inserting into db they block main thread + refreshMap(); + } + + // ///////////////////////////////// DRAGGING PART /////////////////////////////////////// + + public net.osmand.data.RotatedTileBox getCurrentRotatedTileBox() { + return currentViewport; + } + + public float getDensity() { + return currentViewport.getDensity(); + } + + public float getScaleCoefficient() { + float scaleCoefficient = getDensity(); + if (Math.min(dm.widthPixels / (dm.density * 160), dm.heightPixels / (dm.density * 160)) > 2.5f) { + // large screen + scaleCoefficient *= 1.5f; + } + return scaleCoefficient; + } + + /** + * These methods do not consider rotating + */ + protected void dragToAnimate(float fromX, float fromY, float toX, float toY, boolean notify) { + float dx = (fromX - toX); + float dy = (fromY - toY); + moveTo(dx, dy); + if (locationListener != null && notify) { + locationListener.locationChanged(getLatitude(), getLongitude(), this); + } + } + + protected void rotateToAnimate(float rotate) { + if (isMapRotateEnabled()) { + this.rotate = MapUtils.unifyRotationTo360(rotate); + currentViewport.setRotate(this.rotate); + refreshMap(); + } + } + + protected void setLatLonAnimate(double latitude, double longitude, boolean notify) { + currentViewport.setLatLonCenter(latitude, longitude); + refreshMap(); + if (locationListener != null && notify) { + locationListener.locationChanged(latitude, longitude, this); + } + } + + protected void setFractionalZoom(int zoom, double zoomPart, boolean notify) { + currentViewport.setZoomAndAnimation(zoom, 0, zoomPart); + refreshMap(); + if (locationListener != null && notify) { + locationListener.locationChanged(getLatitude(), getLongitude(), this); + } + } + + // for internal usage + protected void zoomToAnimate(int zoom, double zoomToAnimate, boolean notify) { + if (mainLayer != null && getMaxZoom() >= zoom && getMinZoom() <= zoom) { + currentViewport.setZoomAndAnimation(zoom, zoomToAnimate); + if (zoom <= LOWEST_ZOOM_TO_ROTATE) { + rotate = 0; + } + currentViewport.setRotate(rotate); + refreshMap(); + if (notify && locationListener != null) { + locationListener.locationChanged(getLatitude(), getLongitude(), this); + } + } + } + + public void moveTo(float dx, float dy) { + final QuadPoint cp = currentViewport.getCenterPixelPoint(); + final LatLon latlon = currentViewport.getLatLonFromPixel(cp.x + dx, cp.y + dy); + currentViewport.setLatLonCenter(latlon.getLatitude(), latlon.getLongitude()); + refreshMap(); + // do not notify here listener + + } + + public void fitRectToMap(double left, double right, double top, double bottom, + int tileBoxWidthPx, int tileBoxHeightPx, int marginTopPx) { + fitRectToMap(left, right, top, bottom, tileBoxWidthPx, tileBoxHeightPx, marginTopPx, 0); + } + + public void fitRectToMap(double left, double right, double top, double bottom, + int tileBoxWidthPx, int tileBoxHeightPx, int marginTopPx, int marginLeftPx) { + RotatedTileBox tb = currentViewport.copy(); + double border = 0.8; + int dx = 0; + int dy = 0; + + int tbw = (int) (tb.getPixWidth() * border); + int tbh = (int) (tb.getPixHeight() * border); + if (tileBoxWidthPx > 0) { + tbw = (int) (tileBoxWidthPx * border); + if (marginLeftPx > 0) { + dx = (tb.getPixWidth() - tileBoxWidthPx) / 2 - marginLeftPx; + } + } else if (tileBoxHeightPx > 0) { + tbh = (int) (tileBoxHeightPx * border); + dy = (tb.getPixHeight() - tileBoxHeightPx) / 2 - marginTopPx; + } + dy += tb.getCenterPixelY() - tb.getPixHeight() / 2; + tb.setPixelDimensions(tbw, tbh); + + double clat = bottom / 2 + top / 2; + double clon = left / 2 + right / 2; + tb.setLatLonCenter(clat, clon); + while (tb.getZoom() < 17 && tb.containsLatLon(top, left) && tb.containsLatLon(bottom, right)) { + tb.setZoom(tb.getZoom() + 1); + } + while (tb.getZoom() >= 7 && (!tb.containsLatLon(top, left) || !tb.containsLatLon(bottom, right))) { + tb.setZoom(tb.getZoom() - 1); + } + if (dy != 0 || dx != 0) { + clat = tb.getLatFromPixel(tb.getPixWidth() / 2, tb.getPixHeight() / 2 + dy); + clon = tb.getLonFromPixel(tb.getPixWidth() / 2 + dx, tb.getPixHeight() / 2); + } + animatedDraggingThread.startMoving(clat, clon, tb.getZoom(), true); + } + + public RotatedTileBox getTileBox(int tileBoxWidthPx, int tileBoxHeightPx, int marginTopPx) { + RotatedTileBox tb = currentViewport.copy(); + double border = 0.8; + int dy = 0; + + int tbw = (int) (tb.getPixWidth() * border); + int tbh = (int) (tb.getPixHeight() * border); + if (tileBoxWidthPx > 0) { + tbw = (int) (tileBoxWidthPx * border); + } else if (tileBoxHeightPx > 0) { + tbh = (int) (tileBoxHeightPx * border); + dy = (tb.getPixHeight() - tileBoxHeightPx) / 2 - marginTopPx; + } + dy += tb.getCenterPixelY() - tb.getPixHeight() / 2; + tb.setPixelDimensions(tbw, tbh); + + if (dy != 0) { + double clat = tb.getLatFromPixel(tb.getPixWidth() / 2, tb.getPixHeight() / 2 - dy); + double clon = tb.getLonFromPixel(tb.getPixWidth() / 2, tb.getPixHeight() / 2); + tb.setLatLonCenter(clat, clon); + } + return tb; + } + + public void fitLocationToMap(double clat, double clon, int zoom, + int tileBoxWidthPx, int tileBoxHeightPx, int marginTopPx, boolean animated) { + RotatedTileBox tb = currentViewport.copy(); + int dy = 0; + + int tbw = tb.getPixWidth(); + int tbh = tb.getPixHeight(); + if (tileBoxWidthPx > 0) { + tbw = tileBoxWidthPx; + } else if (tileBoxHeightPx > 0) { + tbh = tileBoxHeightPx; + dy = (tb.getPixHeight() - tileBoxHeightPx) / 2 - marginTopPx; + } + dy += tb.getCenterPixelY() - tb.getPixHeight() / 2; + tb.setPixelDimensions(tbw, tbh); + tb.setLatLonCenter(clat, clon); + tb.setZoom(zoom); + if (dy != 0) { + clat = tb.getLatFromPixel(tb.getPixWidth() / 2, tb.getPixHeight() / 2 + dy); + clon = tb.getLonFromPixel(tb.getPixWidth() / 2, tb.getPixHeight() / 2); + } + if (animated) { + animatedDraggingThread.startMoving(clat, clon, tb.getZoom(), true); + } else { + setLatLon(clat, clon); + } + } + + public boolean onGenericMotionEvent(MotionEvent event) { + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 && + event.getAction() == MotionEvent.ACTION_SCROLL && + event.getAxisValue(MotionEvent.AXIS_VSCROLL) != 0) { + final RotatedTileBox tb = getCurrentRotatedTileBox(); + final double lat = tb.getLatFromPixel(event.getX(), event.getY()); + final double lon = tb.getLonFromPixel(event.getX(), event.getY()); + int zoomDir = event.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0 ? -1 : 1; + getAnimatedDraggingThread().startMoving(lat, lon, getZoom() + zoomDir, true); + return true; + } + return false; + } + + public boolean onTouchEvent(MotionEvent event) { + if (mapRenderer != null) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + mapRenderer.suspendSymbolsUpdate(); + } else if (event.getAction() == MotionEvent.ACTION_UP + || event.getAction() == MotionEvent.ACTION_CANCEL) { + mapRenderer.resumeSymbolsUpdate(); + } + } +// if (twoFingersTapDetector.onTouchEvent(event)) { +// ContextMenuLayer contextMenuLayer = getLayerByClass(ContextMenuLayer.class); +// if (contextMenuLayer != null) { +// contextMenuLayer.onTouchEvent(event, getCurrentRotatedTileBox()); +// } +// return true; +// } + + if (event.getAction() == MotionEvent.ACTION_DOWN) { + animatedDraggingThread.stopAnimating(); + } + final boolean isMultiTouch = multiTouchSupport.onTouchEvent(event); + doubleTapScaleDetector.onTouchEvent(event); + if (!isMultiTouch && !doubleTapScaleDetector.isInZoomMode()) { + for (int i = layers.size() - 1; i >= 0; i--) { + layers.get(i).onTouchEvent(event, getCurrentRotatedTileBox()); + } + } + if (!doubleTapScaleDetector.isInZoomMode() && !doubleTapScaleDetector.isDoubleTapping()) { + gestureDetector.onTouchEvent(event); + } + return true; + } + + public void setMapRender(MapRendererView mapRenderer) { + this.mapRenderer = mapRenderer; + } + + public MapRendererView getMapRenderer() { + return mapRenderer; + } + + public Boolean onTrackballEvent(MotionEvent event) { + if (trackBallDelegate != null) { + return trackBallDelegate.onTrackBallEvent(event); + } + return null; + } + + public void setTrackBallDelegate(net.osmand.plus.views.OsmandMapTileView.OnTrackBallListener trackBallDelegate) { + this.trackBallDelegate = trackBallDelegate; + } + + public void setOnLongClickListener(net.osmand.plus.views.OsmandMapTileView.OnLongClickListener l) { + this.onLongClickListener = l; + } + + public void setOnClickListener(net.osmand.plus.views.OsmandMapTileView.OnClickListener l) { + this.onClickListener = l; + } + + public void setAccessibilityActions(AccessibilityActionsProvider actions) { + accessibilityActions = actions; + } + + + public AnimateDraggingMapThread getAnimatedDraggingThread() { + return animatedDraggingThread; + } + + public void showMessage(final String msg) { + handler.post(new Runnable() { + @Override + public void run() { + Toast.makeText(application, msg, Toast.LENGTH_SHORT).show(); //$NON-NLS-1$ + } + }); + } + + + private class MapTileViewMultiTouchZoomListener implements MultiTouchSupport.MultiTouchZoomListener, + DoubleTapScaleDetector.DoubleTapZoomListener { + private PointF initialMultiTouchCenterPoint; + private RotatedTileBox initialViewport; + private float x1; + private float y1; + private float x2; + private float y2; + private LatLon initialCenterLatLon; + private boolean startRotating = false; + private static final float ANGLE_THRESHOLD = 30; + private float initialElevation; + + @Override + public void onZoomOrRotationEnded(double relativeToStart, float angleRelative) { + // 1.5 works better even on dm.density=1 devices + float dz = (float) (Math.log(relativeToStart) / Math.log(2)) * 1.5f; + setIntZoom(Math.round(dz) + initialViewport.getZoom()); + if (Math.abs(angleRelative) < ANGLE_THRESHOLD * relativeToStart || + Math.abs(angleRelative) < ANGLE_THRESHOLD / relativeToStart) { + angleRelative = 0; + } + rotateToAnimate(initialViewport.getRotate() + angleRelative); + final int newZoom = getZoom(); + if (application.accessibilityEnabled()) { + if (newZoom != initialViewport.getZoom()) { + showMessage(application.getString(R.string.zoomIs) + " " + newZoom); //$NON-NLS-1$ + } else { + final LatLon p1 = initialViewport.getLatLonFromPixel(x1, y1); + final LatLon p2 = initialViewport.getLatLonFromPixel(x2, y2); + showMessage(OsmAndFormatter.getFormattedDistance((float) MapUtils.getDistance(p1.getLatitude(), p1.getLongitude(), p2.getLatitude(), p2.getLongitude()), application)); + } + } + } + + @Override + public void onZoomEnded(double relativeToStart) { + // 1.5 works better even on dm.density=1 devices + float dz = (float) ((relativeToStart - 1) * DoubleTapScaleDetector.SCALE_PER_SCREEN); + setIntZoom(Math.round(dz) + initialViewport.getZoom()); + final int newZoom = getZoom(); + if (application.accessibilityEnabled()) { + if (newZoom != initialViewport.getZoom()) { + showMessage(application.getString(R.string.zoomIs) + " " + newZoom); //$NON-NLS-1$ + } else { + final LatLon p1 = initialViewport.getLatLonFromPixel(x1, y1); + final LatLon p2 = initialViewport.getLatLonFromPixel(x2, y2); + showMessage(OsmAndFormatter.getFormattedDistance((float) MapUtils.getDistance(p1.getLatitude(), p1.getLongitude(), p2.getLatitude(), p2.getLongitude()), application)); + } + } + } + + + @Override + public void onGestureInit(float x1, float y1, float x2, float y2) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + if (x1 != x2 || y1 != y2) { + firstTouchPointLatLon = currentViewport.getLatLonFromPixel(x1, y1); + secondTouchPointLatLon = currentViewport.getLatLonFromPixel(x2, y2); + multiTouch = true; + wasZoomInMultiTouch = false; + multiTouchStartTime = System.currentTimeMillis(); + } + } + + @Override + public void onActionPointerUp() { + multiTouch = false; + if (isZooming()) { + wasZoomInMultiTouch = true; + } else { + multiTouchEndTime = System.currentTimeMillis(); + wasZoomInMultiTouch = false; + } + } + + @Override + public void onActionCancel() { + multiTouch = false; + } + + @Override + public void onChangingViewAngle(float angle) { + setElevationAngle(initialElevation - angle); + } + + @Override + public void onChangeViewAngleStarted() { + initialElevation = elevationAngle; + } + + @Override + public void onZoomStarted(PointF centerPoint) { + initialMultiTouchCenterPoint = centerPoint; + initialViewport = getCurrentRotatedTileBox().copy(); + initialCenterLatLon = initialViewport.getLatLonFromPixel(initialMultiTouchCenterPoint.x, + initialMultiTouchCenterPoint.y); + startRotating = false; + } + + @Override + public void onZoomingOrRotating(double relativeToStart, float relAngle) { + double dz = (Math.log(relativeToStart) / Math.log(2)) * 1.5; + if (Math.abs(dz) <= 0.1) { + // keep only rotating + dz = 0; + } + if (Math.abs(relAngle) < ANGLE_THRESHOLD && !startRotating) { + relAngle = 0; + } else { + startRotating = true; + } + if (dz != 0 || relAngle != 0) { + changeZoomPosition((float) dz, relAngle); + } + } + + @Override + public void onZooming(double relativeToStart) { + double dz = (relativeToStart - 1) * DoubleTapScaleDetector.SCALE_PER_SCREEN; + changeZoomPosition((float) dz, 0); + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + LOG.debug("onDoubleTap getZoom()"); + if (!doubleTapScaleDetector.isInZoomMode()) { + if (isZoomingAllowed(getZoom(), 1.1f)) { + final RotatedTileBox tb = getCurrentRotatedTileBox(); + final double lat = tb.getLatFromPixel(e.getX(), e.getY()); + final double lon = tb.getLonFromPixel(e.getX(), e.getY()); + getAnimatedDraggingThread().startMoving(lat, lon, getZoom() + 1, true); + } + afterDoubleTap = true; + return true; + } else { + return false; + } + } + + private void changeZoomPosition(float dz, float angle) { + final RotatedTileBox calc = initialViewport.copy(); + calc.setLatLonCenter(initialCenterLatLon.getLatitude(), initialCenterLatLon.getLongitude()); + float calcRotate = calc.getRotate() + angle; + calc.setRotate(calcRotate); + calc.setZoomAndAnimation(initialViewport.getZoom(), dz, initialViewport.getZoomFloatPart()); + if (multiTouch) { + wasZoomInMultiTouch = true; + } + + final QuadPoint cp = initialViewport.getCenterPixelPoint(); + // Keep zoom center fixed or flexible + LatLon r; + if (multiTouchSupport.isInZoomMode()) { + r = calc.getLatLonFromPixel(cp.x + cp.x - multiTouchSupport.getCenterPoint().x, cp.y + cp.y - multiTouchSupport.getCenterPoint().y); + } else { + r = calc.getLatLonFromPixel(cp.x + cp.x - initialMultiTouchCenterPoint.x, cp.y + cp.y - initialMultiTouchCenterPoint.y); + } + setLatLon(r.getLatitude(), r.getLongitude()); + + int baseZoom = initialViewport.getZoom(); + while (initialViewport.getZoomFloatPart() + dz > 1 && isZoomingAllowed(baseZoom, dz)) { + dz--; + baseZoom++; + } + while (initialViewport.getZoomFloatPart() + dz < 0 && isZoomingAllowed(baseZoom, dz)) { + dz++; + baseZoom--; + } + if (!isZoomingAllowed(baseZoom, dz)) { + dz = Math.signum(dz); + } + + zoomToAnimate(baseZoom, dz, !(doubleTapScaleDetector.isInZoomMode())); + rotateToAnimate(calcRotate); + } + + } + + private void setElevationAngle(float angle) { + if (angle < 35f) { + angle = 35f; + } else if (angle > 90f) { + angle = 90f; + } + this.elevationAngle = angle; + } + + private boolean isZoomingAllowed(int baseZoom, float dz) { + if (baseZoom > getMaxZoom()) { + return false; + } + if (baseZoom > getMaxZoom() - 1 && dz > 1) { + return false; + } + if (baseZoom < getMinZoom()) { + return false; + } + if (baseZoom < getMinZoom() + 1 && dz < -1) { + return false; + } + return true; + } + + public Resources getResources() { + return application.getResources(); + } +} + diff --git a/OsmAnd/src/net/osmand/plus/server/map/POIMapLayerMini.java b/OsmAnd/src/net/osmand/plus/server/map/POIMapLayerMini.java new file mode 100644 index 0000000000..d7b52fde0c --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/server/map/POIMapLayerMini.java @@ -0,0 +1,471 @@ +package net.osmand.plus.server.map; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.PointF; +import android.graphics.drawable.Drawable; +import android.text.util.Linkify; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import net.osmand.AndroidUtils; +import net.osmand.PlatformUtil; +import net.osmand.ResultMatcher; +import net.osmand.ValueHolder; +import net.osmand.data.*; +import net.osmand.osm.PoiType; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.OsmandPlugin; +import net.osmand.plus.R; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.base.PointImageDrawable; +import net.osmand.plus.helpers.WaypointHelper; +import net.osmand.plus.poi.PoiUIFilter; +import net.osmand.plus.render.RenderingIcons; +import net.osmand.plus.routing.IRouteInformationListener; +import net.osmand.plus.routing.RoutingHelper; +import net.osmand.plus.views.OsmandMapLayer; +import net.osmand.plus.views.OsmandMapTileView; +import net.osmand.plus.views.layers.ContextMenuLayer; +import net.osmand.plus.views.layers.MapTextLayer; +import net.osmand.util.Algorithms; + +import java.util.*; + +import static net.osmand.AndroidUtils.dpToPx; + + + import android.app.Dialog; + import android.content.Context; + import android.graphics.Canvas; + import android.graphics.PointF; + import android.graphics.drawable.Drawable; + import android.text.util.Linkify; + import android.util.TypedValue; + import android.view.View; + import android.view.ViewGroup; + import android.widget.LinearLayout; + import android.widget.LinearLayout.LayoutParams; + import android.widget.ScrollView; + import android.widget.TextView; + + import androidx.appcompat.widget.Toolbar; + import androidx.core.content.ContextCompat; + + import net.osmand.AndroidUtils; + import net.osmand.PlatformUtil; + import net.osmand.ResultMatcher; + import net.osmand.ValueHolder; + import net.osmand.data.Amenity; + import net.osmand.data.LatLon; + import net.osmand.data.PointDescription; + import net.osmand.data.QuadRect; + import net.osmand.data.QuadTree; + import net.osmand.data.RotatedTileBox; + import net.osmand.osm.PoiType; + import net.osmand.plus.OsmandApplication; + import net.osmand.plus.OsmandPlugin; + import net.osmand.plus.R; + import net.osmand.plus.activities.MapActivity; + import net.osmand.plus.base.PointImageDrawable; + import net.osmand.plus.helpers.WaypointHelper; + import net.osmand.plus.poi.PoiUIFilter; + import net.osmand.plus.render.RenderingIcons; + import net.osmand.plus.routing.IRouteInformationListener; + import net.osmand.plus.routing.RoutingHelper; + import net.osmand.plus.views.OsmandMapLayer; + import net.osmand.plus.views.OsmandMapTileView; + import net.osmand.plus.views.layers.MapTextLayer.MapTextProvider; + import net.osmand.util.Algorithms; + + import java.util.ArrayList; + import java.util.Collections; + import java.util.Comparator; + import java.util.List; + import java.util.Set; + import java.util.TreeSet; + + import static net.osmand.AndroidUtils.dpToPx; + +public class POIMapLayerMini extends OsmandMapMiniLayer implements ContextMenuLayer.IContextMenuProvider, + MapTextLayer.MapTextProvider, IRouteInformationListener { + private static final int startZoom = 9; + + public static final org.apache.commons.logging.Log log = PlatformUtil.getLog(net.osmand.plus.views.layers.POIMapLayer.class); + + private OsmandMapTileMiniView view; + + private RoutingHelper routingHelper; + private Set filters = new TreeSet<>(); + private MapTextLayer mapTextLayer; + + /// cache for displayed POI + // Work with cache (for map copied from AmenityIndexRepositoryOdb) + private MapLayerData> data; + + private OsmandApplication app; + + public POIMapLayerMini(final OsmandApplication app) { + routingHelper = app.getRoutingHelper(); + routingHelper.addListener(this); + this.app = app; + data = new OsmandMapMiniLayer.MapLayerData>() { + + Set calculatedFilters; + + { + ZOOM_THRESHOLD = 0; + } + + @Override + public boolean isInterrupted() { + return super.isInterrupted(); + } + + @Override + public void layerOnPreExecute() { + calculatedFilters = new TreeSet<>(filters); + } + + @Override + public void layerOnPostExecute() { + + } + + @Override + protected List calculateResult(RotatedTileBox tileBox) { + QuadRect latLonBounds = tileBox.getLatLonBounds(); + if (calculatedFilters.isEmpty() || latLonBounds == null) { + return new ArrayList<>(); + } + int z = (int) Math.floor(tileBox.getZoom() + Math.log(view.getSettings().MAP_DENSITY.get()) / Math.log(2)); + + List res = new ArrayList<>(); + PoiUIFilter.combineStandardPoiFilters(calculatedFilters, app); + for (PoiUIFilter filter : calculatedFilters) { + res.addAll(filter.searchAmenities(latLonBounds.top, latLonBounds.left, + latLonBounds.bottom, latLonBounds.right, z, new ResultMatcher() { + + @Override + public boolean publish(Amenity object) { + return true; + } + + @Override + public boolean isCancelled() { + return isInterrupted(); + } + })); + } + + Collections.sort(res, new Comparator() { + @Override + public int compare(Amenity lhs, Amenity rhs) { + return lhs.getId() < rhs.getId() ? -1 : (lhs.getId().longValue() == rhs.getId().longValue() ? 0 : 1); + } + }); + + return res; + } + }; + } + + public void getAmenityFromPoint(RotatedTileBox tb, PointF point, List am) { + List objects = data.getResults(); + if (objects != null) { + int ex = (int) point.x; + int ey = (int) point.y; + int compare = getScaledTouchRadius(view.getApplication(), getRadiusPoi(tb)); + int radius = compare * 3 / 2; + try { + for (int i = 0; i < objects.size(); i++) { + Amenity n = objects.get(i); + int x = (int) tb.getPixXFromLatLon(n.getLocation().getLatitude(), n.getLocation().getLongitude()); + int y = (int) tb.getPixYFromLatLon(n.getLocation().getLatitude(), n.getLocation().getLongitude()); + if (Math.abs(x - ex) <= compare && Math.abs(y - ey) <= compare) { + compare = radius; + am.add(n); + } + } + } catch (IndexOutOfBoundsException e) { + // that's really rare case, but is much efficient than introduce synchronized block + } + } + } + + @Override + public void initLayer(OsmandMapTileMiniView view) { + this.view = view; + //mapTextLayer = view.getLayerByClass(MapTextLayer.class); + } + + public int getRadiusPoi(RotatedTileBox tb) { + int r; + final double zoom = tb.getZoom(); + if (zoom < startZoom) { + r = 0; + } else if (zoom <= 15) { + r = 10; + } else if (zoom <= 16) { + r = 14; + } else if (zoom <= 17) { + r = 16; + } else { + r = 18; + } + + return (int) (r * view.getScaleCoefficient()); + } + + @Override + public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { + Set selectedPoiFilters = app.getPoiFilters().getSelectedPoiFilters(); + if (this.filters != selectedPoiFilters) { + this.filters = selectedPoiFilters; + data.clearCache(); + } + + List objects = Collections.emptyList(); + List fullObjects = new ArrayList<>(); + List fullObjectsLatLon = new ArrayList<>(); + List smallObjectsLatLon = new ArrayList<>(); + //if (!filters.isEmpty()) { + if (tileBox.getZoom() >= startZoom) { + data.queryNewData(tileBox); + objects = data.getResults(); + if (objects != null) { + float textScale = app.getSettings().TEXT_SCALE.get(); + float iconSize = getIconSize(app) * 1.5f * textScale; + QuadTree boundIntersections = initBoundIntersections(tileBox); + WaypointHelper wph = app.getWaypointHelper(); + PointImageDrawable pointImageDrawable = PointImageDrawable.getOrCreate(app, + ContextCompat.getColor(app, R.color.osmand_orange), true); + pointImageDrawable.setAlpha(0.8f); + for (Amenity o : objects) { + float x = tileBox.getPixXFromLatLon(o.getLocation().getLatitude(), o.getLocation() + .getLongitude()); + float y = tileBox.getPixYFromLatLon(o.getLocation().getLatitude(), o.getLocation() + .getLongitude()); + + if (tileBox.containsPoint(x, y, iconSize)) { + if (intersects(boundIntersections, x, y, iconSize, iconSize) || + (app.getSettings().SHOW_NEARBY_POI.get() && wph.isRouteCalculated() && !wph.isAmenityNoPassed(o))) { + pointImageDrawable.drawSmallPoint(canvas, x, y, textScale); + smallObjectsLatLon.add(new LatLon(o.getLocation().getLatitude(), + o.getLocation().getLongitude())); + } else { + fullObjects.add(o); + fullObjectsLatLon.add(new LatLon(o.getLocation().getLatitude(), + o.getLocation().getLongitude())); + } + } + } + for (Amenity o : fullObjects) { + int x = (int) tileBox.getPixXFromLatLon(o.getLocation().getLatitude(), o.getLocation() + .getLongitude()); + int y = (int) tileBox.getPixYFromLatLon(o.getLocation().getLatitude(), o.getLocation() + .getLongitude()); + if (tileBox.containsPoint(x, y, iconSize)) { + String id = null; + PoiType st = o.getType().getPoiTypeByKeyName(o.getSubType()); + if (st != null) { + if (RenderingIcons.containsSmallIcon(st.getIconKeyName())) { + id = st.getIconKeyName(); + } else if (RenderingIcons.containsSmallIcon(st.getOsmTag() + "_" + st.getOsmValue())) { + id = st.getOsmTag() + "_" + st.getOsmValue(); + } + } + if (id != null) { + pointImageDrawable = PointImageDrawable.getOrCreate(app, + ContextCompat.getColor(app, R.color.osmand_orange), true, + RenderingIcons.getResId(id)); + pointImageDrawable.setAlpha(0.8f); + pointImageDrawable.drawPoint(canvas, x, y, textScale, false); + } + } + } + this.fullObjectsLatLon = fullObjectsLatLon; + this.smallObjectsLatLon = smallObjectsLatLon; + } + } + //} + //mapTextLayer.putData(this, objects); + } + + @Override + public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { + } + + @Override + public void destroyLayer() { + routingHelper.removeListener(this); + } + + @Override + public boolean drawInScreenPixels() { + return true; + } + + public static void showDescriptionDialog(Context ctx, OsmandApplication app, String text, String title) { + showText(ctx, app, text, title); + } + + static int getResIdFromAttribute(final Context ctx, final int attr) { + if (attr == 0) { + return 0; + } + final TypedValue typedvalueattr = new TypedValue(); + ctx.getTheme().resolveAttribute(attr, typedvalueattr, true); + return typedvalueattr.resourceId; + } + + private static void showText(final Context ctx, final OsmandApplication app, final String text, String title) { + final Dialog dialog = new Dialog(ctx, + app.getSettings().isLightContent() ? R.style.OsmandLightTheme : R.style.OsmandDarkTheme); + + LinearLayout ll = new LinearLayout(ctx); + ll.setOrientation(LinearLayout.VERTICAL); + + final Toolbar topBar = new Toolbar(ctx); + topBar.setClickable(true); + Drawable icBack = app.getUIUtilities().getIcon(AndroidUtils.getNavigationIconResId(ctx)); + topBar.setNavigationIcon(icBack); + topBar.setNavigationContentDescription(R.string.access_shared_string_navigate_up); + topBar.setTitle(title); + topBar.setBackgroundColor(ContextCompat.getColor(ctx, getResIdFromAttribute(ctx, R.attr.pstsTabBackground))); + topBar.setTitleTextColor(ContextCompat.getColor(ctx, getResIdFromAttribute(ctx, R.attr.pstsTextColor))); + topBar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(final View v) { + dialog.dismiss(); + } + }); + + final TextView textView = new TextView(ctx); + LinearLayout.LayoutParams llTextParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + int textMargin = dpToPx(app, 10f); + boolean light = app.getSettings().isLightContent(); + textView.setLayoutParams(llTextParams); + textView.setPadding(textMargin, textMargin, textMargin, textMargin); + textView.setTextSize(16); + textView.setTextColor(ContextCompat.getColor(app, light ? R.color.text_color_primary_light : R.color.text_color_primary_dark)); + textView.setAutoLinkMask(Linkify.ALL); + textView.setLinksClickable(true); + textView.setText(text); + + ScrollView scrollView = new ScrollView(ctx); + ll.addView(topBar); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0); + lp.weight = 1; + ll.addView(scrollView, lp); + scrollView.addView(textView); + + dialog.setContentView(ll); + dialog.setCancelable(true); + dialog.show(); + } + + @Override + public PointDescription getObjectName(Object o) { + if (o instanceof Amenity) { + Amenity amenity = (Amenity) o; + String preferredLang = app.getSettings().MAP_PREFERRED_LOCALE.get(); + boolean transliterateNames = app.getSettings().MAP_TRANSLITERATE_NAMES.get(); + + if (amenity.getType().isWiki()) { + if (Algorithms.isEmpty(preferredLang)) { + preferredLang = app.getLanguage(); + } + preferredLang = OsmandPlugin.onGetMapObjectsLocale(amenity, preferredLang); + } + + return new PointDescription(PointDescription.POINT_TYPE_POI, + amenity.getName(preferredLang, transliterateNames)); + } + return null; + } + + @Override + public boolean disableSingleTap() { + return false; + } + + @Override + public boolean disableLongPressOnMap() { + return false; + } + + @Override + public void collectObjectsFromPoint(PointF point, RotatedTileBox tileBox, List objects, boolean unknownLocation) { + if (tileBox.getZoom() >= startZoom) { + getAmenityFromPoint(tileBox, point, objects); + } + } + + @Override + public LatLon getObjectLocation(Object o) { + if (o instanceof Amenity) { + return ((Amenity) o).getLocation(); + } + return null; + } + + @Override + public boolean isObjectClickable(Object o) { + return o instanceof Amenity; + } + + @Override + public boolean runExclusiveAction(Object o, boolean unknownLocation) { + return false; + } + + @Override + public LatLon getTextLocation(Amenity o) { + return o.getLocation(); + } + + @Override + public int getTextShift(Amenity amenity, RotatedTileBox rb) { + int radiusPoi = getRadiusPoi(rb); + if (isPresentInFullObjects(amenity.getLocation())) { + radiusPoi += (getIconSize(app) - app.getResources().getDimensionPixelSize(R.dimen.favorites_icon_size_small)) / 2; + } + return radiusPoi; + } + + @Override + public String getText(Amenity o) { + return o.getName(view.getSettings().MAP_PREFERRED_LOCALE.get(), + view.getSettings().MAP_TRANSLITERATE_NAMES.get()); + } + + @Override + public boolean isTextVisible() { + return app.getSettings().SHOW_POI_LABEL.get(); + } + + @Override + public boolean isFakeBoldText() { + return false; + } + + @Override + public void newRouteIsCalculated(boolean newRoute, ValueHolder showToast) { + } + + @Override + public void routeWasCancelled() { + } + + @Override + public void routeWasFinished() { + } +} +