package net.osmand.plus.views; import gnu.trove.list.array.TIntArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import net.osmand.data.LatLon; import net.osmand.data.QuadRect; import net.osmand.data.QuadTree; import net.osmand.data.RotatedTileBox; import net.osmand.plus.ContextMenuAdapter; import net.osmand.plus.OsmandApplication; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.render.OsmandRenderer; import net.osmand.plus.render.OsmandRenderer.RenderingContext; import net.osmand.render.RenderingRuleSearchRequest; import net.osmand.render.RenderingRulesStorage; import net.osmand.util.MapAlgorithms; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Paint.Cap; import android.graphics.Paint.Join; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.PointF; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter; import android.os.AsyncTask; import android.support.annotation.NonNull; import android.view.MotionEvent; public abstract class OsmandMapLayer { protected List<LatLon> fullObjectsLatLon; protected List<LatLon> smallObjectsLatLon; public abstract void initLayer(OsmandMapTileView 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<String, Object> 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 <Params> void executeTaskInBackground(AsyncTask<Params, ?, ?> task, Params... params) { task.execute(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, net.osmand.plus.views.OsmandMapLayer.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; } } protected boolean isIn(int x, int y, int lx, int ty, int rx, int by) { return x >= lx && x <= rx && y >= ty && y <= by; } public int calculatePath(RotatedTileBox tb, TIntArrayList xs, TIntArrayList ys, Path path) { boolean start = false; int px = xs.get(0); int py = ys.get(0); int h = tb.getPixHeight(); int w = tb.getPixWidth(); int cnt = 0; boolean pin = isIn(px, py, 0, 0, w, h); for (int i = 1; i < xs.size(); i++) { int x = xs.get(i); int y = ys.get(i); boolean in = isIn(x, y, 0, 0, w, h); boolean draw = false; if (pin && in) { draw = true; } else { long intersection = MapAlgorithms.calculateIntersection(x, y, px, py, 0, w, h, 0); if (intersection != -1) { if (pin && (i == 1)) { cnt++; path.moveTo(px, py); start = true; } px = (int) (intersection >> 32); py = (int) (intersection & 0xffffffff); draw = true; } } if (draw) { if (!start) { cnt++; path.moveTo(px, py); } path.lineTo(x, y); start = true; } else { start = false; } pin = in; px = x; py = y; } return cnt; } @NonNull public QuadTree<QuadRect> 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 boolean intersects(QuadTree<QuadRect> boundIntersections, float x, float y, float width, float height) { List<QuadRect> 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 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 abstract class MapLayerData<T> { 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 layerOnPostExecute() { } public boolean isInterrupted() { return pendingTask != null; } protected abstract T calculateResult(RotatedTileBox tileBox); public class Task extends AsyncTask<Object, Object, T> { 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; } @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; } } protected static class RenderingLineAttributes { protected int cachedHash; public Paint paint; 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(); paint2 = initPaint(); paint3 = initPaint(); paint_1 = initPaint(); shadowPaint = initPaint(); } private Paint initPaint() { Paint paint = new Paint(); paint.setStyle(Style.STROKE); paint.setAntiAlias(true); paint.setStrokeCap(Cap.ROUND); paint.setStrokeJoin(Join.ROUND); return paint; } public boolean updatePaints(OsmandMapTileView view, DrawSettings settings, RotatedTileBox tileBox) { OsmandApplication app = view.getApplication(); 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)) { 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), 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); } } 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); } canvas.drawPath(path, paint); if (isPaint2) { canvas.drawPath(path, paint2); } if (isPaint3) { canvas.drawPath(path, paint3); } } } }