package net.osmand.plus.render;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.osmand.NativeLibrary;
import net.osmand.NativeLibrary.NativeSearchResult;
import net.osmand.PlatformUtil;
import net.osmand.binary.BinaryMapDataObject;
import net.osmand.binary.BinaryMapIndexReader.TagValuePair;
import net.osmand.data.QuadRect;
import net.osmand.data.QuadTree;
import net.osmand.map.MapTileDownloader;
import net.osmand.map.MapTileDownloader.IMapDownloaderCallback;
import net.osmand.plus.render.TextRenderer.TextDrawInfo;
import net.osmand.render.RenderingRuleProperty;
import net.osmand.render.RenderingRuleSearchRequest;
import net.osmand.render.RenderingRulesStorage;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Path.FillType;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.view.WindowManager;
public class OsmandRenderer {
private static final Log log = PlatformUtil.getLog(OsmandRenderer.class);
private Paint paint;
private Paint paintIcon;
public static final int DEFAULT_POLYGON_MAX = 11;
public static final int DEFAULT_LINE_MAX = 100;
public static final int DEFAULT_POINTS_MAX = 200;
public static final int TILE_SIZE = 256;
private static final int MAX_V = 10;
private static final int MAX_V_AREA = 2000;
private Map<float[], PathEffect> dashEffect = new LinkedHashMap<float[], PathEffect>();
private Map<String, float[]> parsedDashEffects = new LinkedHashMap<String, float[]>();
private Map<String, Shader> shaders = new LinkedHashMap<String, Shader>();
private final Context context;
private DisplayMetrics dm;
private TextRenderer textRenderer;
public class MapDataObjectPrimitive {
BinaryMapDataObject obj;
int typeInd;
double order;
double area;
int objectType;
};
private static class IconDrawInfo {
float x = 0;
float y = 0;
String resId_1;
String resId;
String resId2;
String resId3;
String resId4;
String resId5;
String shieldId;
int iconOrder;
float iconSize;
}
/* package */
public static class RenderingContext extends net.osmand.RenderingContext {
List<TextDrawInfo> textToDraw = new ArrayList<TextDrawInfo>();
List<IconDrawInfo> iconsToDraw = new ArrayList<IconDrawInfo>();
Paint[] oneWay ;
Paint[] reverseOneWay ;
final Context ctx;
public RenderingContext(Context ctx) {
this.ctx = ctx;
}
// use to calculate points
PointF tempPoint = new PointF();
float cosRotateTileSize;
float sinRotateTileSize;
int shadowLevelMin = 256;
int shadowLevelMax = 0;
boolean ended = false;
@Override
protected byte[] getIconRawData(String data) {
return RenderingIcons.getIconRawData(ctx, data);
}
}
public OsmandRenderer(Context context) {
this.context = context;
paintIcon = new Paint();
paintIcon.setStyle(Style.STROKE);
textRenderer = new TextRenderer(context);
paint = new Paint();
paint.setAntiAlias(true);
dm = new DisplayMetrics();
WindowManager wmgr = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wmgr.getDefaultDisplay().getMetrics(dm);
}
public PathEffect getDashEffect(RenderingContext rc, float[] cachedValues, float st){
float[] dashes = new float[cachedValues.length / 2];
for (int i = 0; i < dashes.length; i++) {
dashes[i] = rc.getDensityValue(cachedValues[i * 2]) + cachedValues[i * 2 + 1];
}
if(!dashEffect.containsKey(dashes)){
dashEffect.put(dashes, new DashPathEffect(dashes, st));
}
return dashEffect.get(dashes);
}
public Shader getShader(String resId){
if(shaders.get(resId) == null){
Bitmap bmp = RenderingIcons.getIcon(context, resId, true);
if(bmp != null){
Shader sh = new BitmapShader(bmp, TileMode.REPEAT, TileMode.REPEAT);
shaders.put(resId, sh);
} else {
shaders.put(resId, null);
}
}
return shaders.get(resId);
}
private void put(TIntObjectHashMap<TIntArrayList> map, int k, int v){
if(!map.containsKey(k)){
map.put(k, new TIntArrayList());
}
map.get(k).add(v);
}
/**
* @return if map could be replaced
*/
public void generateNewBitmapNative(RenderingContext rc, NativeOsmandLibrary library,
NativeSearchResult searchResultHandler,
Bitmap bmp, RenderingRuleSearchRequest render, final MapTileDownloader mapTileDownloader) {
long now = System.currentTimeMillis();
if (rc.width > 0 && rc.height > 0 && searchResultHandler != null) {
rc.cosRotateTileSize = (float) (Math.cos(Math.toRadians(rc.rotate)) * TILE_SIZE);
rc.sinRotateTileSize = (float) (Math.sin(Math.toRadians(rc.rotate)) * TILE_SIZE);
try {
if(Looper.getMainLooper() != null && library.useDirectRendering()) {
final Handler h = new Handler(Looper.getMainLooper());
notifyListenersWithDelay(rc, mapTileDownloader, h);
}
// Native library will decide on it's own best way of rendering
// If res.bitmapBuffer is null, it indicates that rendering was done directly to
// memory of passed bitmap, but this is supported only on Android >= 2.2
final NativeLibrary.RenderingGenerationResult res = library.generateRendering(
rc, searchResultHandler, bmp, bmp.hasAlpha(), render);
rc.ended = true;
notifyListeners(mapTileDownloader);
long time = System.currentTimeMillis() - now;
rc.renderingDebugInfo = String.format("Rendering: %s ms (%s text)\n"
+ "(%s points, %s points inside, %s of %s objects visible)\n",//$NON-NLS-1$
time, rc.textRenderingTime, rc.pointCount, rc.pointInsideCount, rc.visible, rc.allObjects);
// See upper note
if(res.bitmapBuffer != null) {
bmp.copyPixelsFromBuffer(res.bitmapBuffer);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
void drawObject(RenderingContext rc, Canvas cv, RenderingRuleSearchRequest req,
List<MapDataObjectPrimitive> array, int objOrder) {
//double polygonLimit = 100;
//float orderToSwitch = 0;
double minPolygonSize = 1. / rc.polygonMinSizeToDisplay;
for (int i = 0; i < array.size(); i++) {
rc.allObjects++;
BinaryMapDataObject mObj = array.get(i).obj;
TagValuePair pair = mObj.getMapIndex().decodeType(mObj.getTypes()[array.get(i).typeInd]);
if (array.get(i).objectType == 3) {
if (array.get(i).order > minPolygonSize + ((int) array.get(i).order)) {
continue;
}
// polygon
drawPolygon(mObj, req, cv, rc, pair, array.get(i).area);
} else if (array.get(i).objectType == 2) {
drawPolyline(mObj, req, cv, rc, pair, mObj.getSimpleLayer(), objOrder == 1);
} else if (array.get(i).objectType == 1) {
drawPoint(mObj, req, cv, rc, pair, array.get(i).typeInd == 0);
}
if (i % 25 == 0 && rc.interrupted) {
return;
}
}
}
public void generateNewBitmap(RenderingContext rc, List<BinaryMapDataObject> objects, Bitmap bmp,
RenderingRuleSearchRequest render, final MapTileDownloader mapTileDownloader) {
long now = System.currentTimeMillis();
// fill area
Canvas cv = new Canvas(bmp);
if (rc.defaultColor != 0) {
cv.drawColor(rc.defaultColor);
}
if (objects != null && !objects.isEmpty() && rc.width > 0 && rc.height > 0) {
rc.cosRotateTileSize = (float) (Math.cos((float) Math.toRadians(rc.rotate)) * TILE_SIZE);
rc.sinRotateTileSize = (float) (Math.sin((float) Math.toRadians(rc.rotate)) * TILE_SIZE);
// put in order map
List<MapDataObjectPrimitive> pointsArray = new ArrayList<OsmandRenderer.MapDataObjectPrimitive>();
List<MapDataObjectPrimitive> polygonsArray = new ArrayList<OsmandRenderer.MapDataObjectPrimitive>();
List<MapDataObjectPrimitive> linesArray = new ArrayList<OsmandRenderer.MapDataObjectPrimitive>();
sortObjectsByProperOrder(rc, objects, render, pointsArray, polygonsArray, linesArray);
rc.lastRenderedKey = 0;
drawObject(rc, cv, render, polygonsArray, 0);
rc.lastRenderedKey = DEFAULT_POLYGON_MAX;
if (rc.shadowRenderingMode > 1) {
drawObject(rc, cv, render, linesArray, 1);
}
rc.lastRenderedKey = (DEFAULT_LINE_MAX + DEFAULT_POLYGON_MAX) / 2;
drawObject(rc, cv, render, linesArray, 2);
rc.lastRenderedKey = DEFAULT_LINE_MAX;
drawObject(rc, cv, render, pointsArray, 3);
rc.lastRenderedKey = DEFAULT_POINTS_MAX;
long beforeIconTextTime = System.currentTimeMillis() - now;
notifyListeners(mapTileDownloader);
drawIconsOverCanvas(rc, cv);
notifyListeners(mapTileDownloader);
textRenderer.drawTextOverCanvas(rc, cv, rc.preferredLocale);
long time = System.currentTimeMillis() - now;
rc.renderingDebugInfo = String.format("Rendering: %s ms (%s text)\n"
+ "(%s points, %s points inside, %s of %s objects visible)",//$NON-NLS-1$
time, time - beforeIconTextTime, rc.pointCount, rc.pointInsideCount, rc.visible, rc.allObjects);
log.info(rc.renderingDebugInfo);
}
}
private void notifyListenersWithDelay(final RenderingContext rc, final MapTileDownloader mapTileDownloader, final Handler h) {
h.postDelayed(new Runnable() {
@Override
public void run() {
if(!rc.ended) {
notifyListeners(mapTileDownloader);
notifyListenersWithDelay(rc, mapTileDownloader, h);
}
}
}, 800);
}
public float getDensity(){
return dm.density;
}
private void drawIconsOverCanvas(RenderingContext rc, Canvas cv) {
// 1. Sort text using text order
Collections.sort(rc.iconsToDraw, new Comparator<IconDrawInfo>() {
@Override
public int compare(IconDrawInfo object1, IconDrawInfo object2) {
return object1.iconOrder - object2.iconOrder;
}
});
QuadRect bounds = new QuadRect(0, 0, rc.width, rc.height);
bounds.inset(-bounds.width()/4, -bounds.height()/4);
QuadTree<RectF> boundIntersections = new QuadTree<RectF>(bounds, 4, 0.6f);
List<RectF> result = new ArrayList<RectF>();
for (IconDrawInfo icon : rc.iconsToDraw) {
if (icon.resId != null) {
Bitmap ico = RenderingIcons.getIcon(context, icon.resId, true);
if (ico != null) {
if (icon.y >= 0 && icon.y < rc.height && icon.x >= 0 && icon.x < rc.width) {
int visbleWidth = icon.iconSize >= 0 ? (int) icon.iconSize : ico.getWidth();
int visbleHeight = icon.iconSize >= 0 ? (int) icon.iconSize : ico.getHeight();
boolean intersects = false;
float coeff = rc.getDensityValue(rc.screenDensityRatio * rc.textScale);
RectF rf = calculateRect(rc, icon, ico.getWidth(), ico.getHeight());
RectF visibleRect = null;
if (visbleHeight > 0 && visbleWidth > 0) {
visibleRect = calculateRect(rc, icon, visbleWidth, visbleHeight);
boundIntersections.queryInBox(new QuadRect(visibleRect.left, visibleRect.top, visibleRect.right, visibleRect.bottom), result);
for (RectF r : result) {
if (r.intersects(visibleRect.left, visibleRect.top, visibleRect.right, visibleRect.bottom)) {
intersects = true;
break;
}
}
}
if (!intersects) {
Bitmap shield = icon.shieldId == null ? null : RenderingIcons.getIcon(context, icon.shieldId, true);
if(shield != null) {
RectF shieldRf = calculateRect(rc, icon, shield.getWidth(), shield.getHeight());
if (coeff != 1f) {
Rect src = new Rect(0, 0, shield.getWidth(), shield.getHeight());
drawBitmap(cv, shield, shieldRf, src);
} else {
drawBitmap(cv, shield, shieldRf);
}
}
if (coeff != 1f) {
Rect src = new Rect(0, 0, ico.getWidth(), ico.getHeight());
drawBitmap(cv, RenderingIcons.getIcon(context, icon.resId_1, true), rf, src);
drawBitmap(cv, ico, rf, src);
drawBitmap(cv, RenderingIcons.getIcon(context, icon.resId2, true), rf, src);
drawBitmap(cv, RenderingIcons.getIcon(context, icon.resId3, true), rf, src);
drawBitmap(cv, RenderingIcons.getIcon(context, icon.resId4, true), rf, src);
drawBitmap(cv, RenderingIcons.getIcon(context, icon.resId5, true), rf, src);
} else {
drawBitmap(cv, RenderingIcons.getIcon(context, icon.resId_1, true), rf);
drawBitmap(cv, ico, rf);
drawBitmap(cv, RenderingIcons.getIcon(context, icon.resId2, true), rf);
drawBitmap(cv, RenderingIcons.getIcon(context, icon.resId3, true), rf);
drawBitmap(cv, RenderingIcons.getIcon(context, icon.resId4, true), rf);
drawBitmap(cv, RenderingIcons.getIcon(context, icon.resId5, true), rf);
}
if(visibleRect != null) {
visibleRect.inset(-visibleRect.width() / 4, -visibleRect.height() / 4);
boundIntersections.insert(visibleRect,
new QuadRect(visibleRect.left, visibleRect.top, visibleRect.right, visibleRect.bottom));
}
}
}
}
}
if (rc.interrupted) {
return;
}
}
}
protected void drawBitmap(Canvas cv, Bitmap ico, RectF rf) {
if(ico == null) {
return;
}
cv.drawBitmap(ico, rf.left, rf.top, paintIcon);
}
protected void drawBitmap(Canvas cv, Bitmap ico, RectF rf, Rect src) {
if(ico == null) {
return;
}
cv.drawBitmap(ico, src, rf, paintIcon);
}
private RectF calculateRect(RenderingContext rc, IconDrawInfo icon, int visbleWidth, int visbleHeight) {
RectF rf;
float coeff = rc.getDensityValue(rc.screenDensityRatio * rc.textScale);
float left = icon.x - visbleWidth / 2 * coeff;
float top = icon.y - visbleHeight / 2 * coeff;
float right = left + visbleWidth * coeff;
float bottom = top + visbleHeight * coeff;
rf = new RectF(left, top, right, bottom);
return rf;
}
Comparator<MapDataObjectPrimitive> sortByOrder() {
return new Comparator<MapDataObjectPrimitive>() {
@Override
public int compare(MapDataObjectPrimitive i, MapDataObjectPrimitive j) {
if (i.order == j.order) {
if (i.typeInd == j.typeInd) {
if(i.obj.getPointsLength() == j.obj.getPointsLength()) {
return 0;
}
return i.obj.getPointsLength() < j.obj.getPointsLength() ? -1 : 1;
}
return i.typeInd < j.typeInd ? -1 : 1;
}
return (i.order < j.order ? -1 : 1);
}
};
}
Comparator<MapDataObjectPrimitive> sortPolygonsOrder() {
return new Comparator<MapDataObjectPrimitive>() {
@Override
public int compare(MapDataObjectPrimitive i, MapDataObjectPrimitive j) {
if (i.order == j.order)
return i.typeInd < j.typeInd ? -1 : 1;
return (i.order > j.order) ? -1 : 1;
}
};
}
private void sortObjectsByProperOrder(RenderingContext rc, List<BinaryMapDataObject> objects,
RenderingRuleSearchRequest render,
List<MapDataObjectPrimitive> pointsArray, List<MapDataObjectPrimitive> polygonsArray,
List<MapDataObjectPrimitive> linesResArray) {
int sz = objects.size();
List<MapDataObjectPrimitive> linesArray = new ArrayList<OsmandRenderer.MapDataObjectPrimitive>();
if (render != null) {
render.clearState();
float mult = (float) (1. / MapUtils.getPowZoom(Math.max(31 - (rc.zoom + 8), 0)));
for (int i = 0; i < sz; i++) {
BinaryMapDataObject o = objects.get(i);
for (int j = 0; j < o.getTypes().length; j++) {
int wholeType = o.getTypes()[j];
int layer = 0;
if (o.getPointsLength() > 1) {
layer = o.getSimpleLayer();
}
TagValuePair pair = o.getMapIndex().decodeType(wholeType);
if (pair != null) {
render.setTagValueZoomLayer(pair.tag, pair.value, rc.zoom, layer, o);
render.setBooleanFilter(render.ALL.R_AREA, o.isArea());
render.setBooleanFilter(render.ALL.R_POINT, o.getPointsLength() == 1);
render.setBooleanFilter(render.ALL.R_CYCLE, o.isCycle());
if (render.search(RenderingRulesStorage.ORDER_RULES)) {
int objectType = render.getIntPropertyValue(render.ALL.R_OBJECT_TYPE);
boolean ignorePointArea = render.getIntPropertyValue(render.ALL.R_IGNORE_POLYGON_AS_POINT_AREA) != 0;
int order = render.getIntPropertyValue(render.ALL.R_ORDER);
MapDataObjectPrimitive mapObj = new MapDataObjectPrimitive();
mapObj.objectType = objectType;
mapObj.order = order;
mapObj.typeInd = j;
mapObj.obj = o;
if(objectType == 3) {
MapDataObjectPrimitive pointObj = mapObj;
pointObj.objectType = 1;
double area = polygonArea(mapObj, mult);
mapObj.area = area;
if(area > MAX_V) {
mapObj.order = mapObj.order + (1. / area);
if(order < DEFAULT_POLYGON_MAX) {
polygonsArray.add(mapObj);
} else {
linesArray.add(mapObj);
}
if(area > MAX_V_AREA || ignorePointArea) {
pointsArray.add(pointObj);
}
}
} else if(objectType == 1) {
pointsArray.add(mapObj);
} else {
linesArray.add(mapObj);
}
if (render.isSpecified(render.ALL.R_SHADOW_LEVEL)) {
rc.shadowLevelMin = Math.min(rc.shadowLevelMin, order);
rc.shadowLevelMax = Math.max(rc.shadowLevelMax, order);
render.clearValue(render.ALL.R_SHADOW_LEVEL);
}
}
}
}
if (rc.interrupted) {
return;
}
}
}
Collections.sort(polygonsArray, sortByOrder());
Collections.sort(pointsArray, sortByOrder());
Collections.sort(linesArray, sortByOrder());
filterLinesByDensity(rc, linesResArray, linesArray);
}
void filterLinesByDensity(RenderingContext rc, List<MapDataObjectPrimitive> linesResArray,
List<MapDataObjectPrimitive> linesArray) {
// int roadsLimit = rc->roadsDensityLimitPerTile;
// int densityZ = rc->roadDensityZoomTile;
// if(densityZ == 0 || roadsLimit == 0) {
// linesResArray = linesArray;
// return;
// }
// linesResArray.reserve(linesArray.size());
// UNORDERED(map)<int64_t, pair<int, int> > densityMap;
// for (int i = linesArray.size() - 1; i >= 0; i--) {
// bool accept = true;
// int o = linesArray[i].order;
// MapDataObject* line = linesArray[i].obj;
// tag_value& ts = line->types[linesArray[i].typeInd];
// if (ts.first == "highway") {
// accept = false;
// int64_t prev = 0;
// for (uint k = 0; k < line->points.size(); k++) {
// int dz = rc->getZoom() + densityZ;
// int64_t x = (line->points[k].first) >> (31 - dz);
// int64_t y = (line->points[k].second) >> (31 - dz);
// int64_t tl = (x << dz) + y;
// if (prev != tl) {
// prev = tl;
// pair<int, int>& p = densityMap[tl];
// if (p.first < roadsLimit/* && p.second > o */) {
// accept = true;
// p.first++;
// p.second = o;
// densityMap[tl] = p;
// }
// }
// }
// }
// if(accept) {
// linesResArray.push_back(linesArray[i]);
// }
// }
// reverse(linesResArray.begin(), linesResArray.end());
// TODO
linesResArray.addAll(linesArray);
}
private double polygonArea(MapDataObjectPrimitive mapObj, float mult) {
double area = 0.;
int j = mapObj.obj.getPointsLength() - 1;
for (int i = 0; i < mapObj.obj.getPointsLength(); i++) {
int px = mapObj.obj.getPoint31XTile(i);
int py = mapObj.obj.getPoint31YTile(i);
int sx = mapObj.obj.getPoint31XTile(j);
int sy = mapObj.obj.getPoint31YTile(j);
area += (sx + ((float) px)) * (sy - ((float) py));
j = i;
}
return Math.abs(area) * mult * mult * .5;
}
private void notifyListeners(MapTileDownloader mapTileDownloader) {
if (mapTileDownloader != null) {
mapTileDownloader.fireLoadCallback(null);
}
}
private PointF calcPoint(int xt, int yt, RenderingContext rc){
rc.pointCount ++;
double tx = xt / rc.tileDivisor;
double ty = yt / rc.tileDivisor;
double dTileX = (tx - rc.leftX);
double dTileY = (ty - rc.topY);
float x = (float) (rc.cosRotateTileSize * dTileX - rc.sinRotateTileSize * dTileY);
float y = (float) (rc.sinRotateTileSize * dTileX + rc.cosRotateTileSize * dTileY);
rc.tempPoint.set(x, y);
if(rc.tempPoint.x >= 0 && rc.tempPoint.x < rc.width &&
rc.tempPoint.y >= 0 && rc.tempPoint.y < rc.height){
rc.pointInsideCount++;
}
return rc.tempPoint;
}
private PointF calcPoint(BinaryMapDataObject o, int ind, RenderingContext rc){
return calcPoint(o.getPoint31XTile(ind), o.getPoint31YTile(ind), rc);
}
public void clearCachedResources(){
shaders.clear();
}
private void drawPolygon(BinaryMapDataObject obj, RenderingRuleSearchRequest render, Canvas canvas, RenderingContext rc, TagValuePair pair,
double area) {
if(render == null || pair == null){
return;
}
float xText = 0;
float yText = 0;
int zoom = rc.zoom;
Path path = null;
// rc.main.color = Color.rgb(245, 245, 245);
render.setInitialTagValueZoom(pair.tag, pair.value, zoom, obj);
boolean rendered = render.search(RenderingRulesStorage.POLYGON_RULES);
if(!rendered || !updatePaint(render, paint, 0, true, rc)){
return;
}
rc.visible++;
int len = obj.getPointsLength();
// if(len > 150) {
// int[] ts = obj.getTypes();
// System.err.println("Polygon " + len);
// for(int i=0; i<ts.length; i++) {
// System.err.println(obj.getMapIndex().decodeType(ts[i]));
// }
// return;
// }
for (int i = 0; i < obj.getPointsLength(); i++) {
PointF p = calcPoint(obj, i, rc);
xText += p.x;
yText += p.y;
if (path == null) {
path = new Path();
path.moveTo(p.x, p.y);
} else {
path.lineTo(p.x, p.y);
}
}
int[][] polygonInnerCoordinates = obj.getPolygonInnerCoordinates();
if (polygonInnerCoordinates != null && path != null) {
path.setFillType(FillType.EVEN_ODD);
for (int j = 0; j < polygonInnerCoordinates.length; j++) {
for (int i = 0; i < polygonInnerCoordinates[j].length; i += 2) {
PointF p = calcPoint(polygonInnerCoordinates[j][i], polygonInnerCoordinates[j][i + 1], rc);
if (i == 0) {
path.moveTo(p.x, p.y);
} else {
path.lineTo(p.x, p.y);
}
}
}
}
if (path != null && len > 0) {
canvas.drawPath(path, paint);
if (updatePaint(render, paint, 1, false, rc)) {
canvas.drawPath(path, paint);
}
boolean ignorePointArea = render.getIntPropertyValue(render.ALL.R_IGNORE_POLYGON_AS_POINT_AREA) != 0;
if(area > MAX_V_AREA || ignorePointArea) {
textRenderer.renderText(obj, render, rc, pair, xText / len, yText / len, null, null);
}
}
}
public boolean updatePaint(RenderingRuleSearchRequest req, Paint p, int ind, boolean area, RenderingContext rc){
RenderingRuleProperty rColor;
RenderingRuleProperty rStrokeW;
RenderingRuleProperty rCap;
RenderingRuleProperty rPathEff;
if (ind == 0) {
rColor = req.ALL.R_COLOR;
rStrokeW = req.ALL.R_STROKE_WIDTH;
rCap = req.ALL.R_CAP;
rPathEff = req.ALL.R_PATH_EFFECT;
} else if(ind == 1){
rColor = req.ALL.R_COLOR_2;
rStrokeW = req.ALL.R_STROKE_WIDTH_2;
rCap = req.ALL.R_CAP_2;
rPathEff = req.ALL.R_PATH_EFFECT_2;
} else if(ind == -1){
rColor = req.ALL.R_COLOR_0;
rStrokeW = req.ALL.R_STROKE_WIDTH_0;
rCap = req.ALL.R_CAP_0;
rPathEff = req.ALL.R_PATH_EFFECT_0;
} else if(ind == -2){
rColor = req.ALL.R_COLOR__1;
rStrokeW = req.ALL.R_STROKE_WIDTH__1;
rCap = req.ALL.R_CAP__1;
rPathEff = req.ALL.R_PATH_EFFECT__1;
} else if(ind == 2){
rColor = req.ALL.R_COLOR_3;
rStrokeW = req.ALL.R_STROKE_WIDTH_3;
rCap = req.ALL.R_CAP_3;
rPathEff = req.ALL.R_PATH_EFFECT_3;
} else if(ind == -3){
rColor = req.ALL.R_COLOR__2;
rStrokeW = req.ALL.R_STROKE_WIDTH__2;
rCap = req.ALL.R_CAP__2;
rPathEff = req.ALL.R_PATH_EFFECT__2;
} else if(ind == 3){
rColor = req.ALL.R_COLOR_4;
rStrokeW = req.ALL.R_STROKE_WIDTH_4;
rCap = req.ALL.R_CAP_4;
rPathEff = req.ALL.R_PATH_EFFECT_4;
} else {
rColor = req.ALL.R_COLOR_5;
rStrokeW = req.ALL.R_STROKE_WIDTH_5;
rCap = req.ALL.R_CAP_5;
rPathEff = req.ALL.R_PATH_EFFECT_5;
}
if(area){
if(!req.isSpecified(rColor) && !req.isSpecified(req.ALL.R_SHADER)){
return false;
}
p.setShader(null);
p.setColorFilter(null);
p.clearShadowLayer();
p.setStyle(Style.FILL_AND_STROKE);
p.setStrokeWidth(0);
} else {
if(!req.isSpecified(rStrokeW)){
return false;
}
p.setShader(null);
p.setColorFilter(null);
p.clearShadowLayer();
p.setStyle(Style.STROKE);
p.setStrokeWidth(rc.getComplexValue(req, rStrokeW));
String cap = req.getStringPropertyValue(rCap);
if(!Algorithms.isEmpty(cap)){
p.setStrokeCap(Cap.valueOf(cap.toUpperCase()));
} else {
p.setStrokeCap(Cap.BUTT);
}
String pathEffect = req.getStringPropertyValue(rPathEff);
if (!Algorithms.isEmpty(pathEffect)) {
if(!parsedDashEffects.containsKey(pathEffect)) {
String[] vls = pathEffect.split("_");
float[] vs = new float[vls.length * 2];
for(int i = 0; i < vls.length; i++) {
int s = vls[i].indexOf(':');
String pre = vls[i];
String post = "";
if(s != -1) {
pre = vls[i].substring(0, i);
post = vls[i].substring(i + 1);
}
if(pre.length() > 0) {
vs[i*2 ] = Float.parseFloat(pre);
}
if(post.length() > 0) {
vs[i*2 +1] = Float.parseFloat(post);
}
}
parsedDashEffects.put(pathEffect, vs);
}
float[] cachedValues = parsedDashEffects.get(pathEffect);
p.setPathEffect(getDashEffect(rc, cachedValues, 0));
} else {
p.setPathEffect(null);
}
}
p.setColor(req.getIntPropertyValue(rColor));
if(ind == 0){
String resId = req.getStringPropertyValue(req.ALL.R_SHADER);
if(resId != null){
if(req.getIntPropertyValue(rColor) == 0) {
p.setColor(Color.WHITE); // set color required by skia
}
p.setShader(getShader(resId));
}
// do not check shadow color here
if(rc.shadowRenderingMode == 1) {
int shadowColor = req.getIntPropertyValue(req.ALL.R_SHADOW_COLOR);
if(shadowColor == 0) {
shadowColor = rc.shadowRenderingColor;
}
int shadowRadius = (int) rc.getComplexValue(req, req.ALL.R_SHADOW_RADIUS);
if (shadowColor == 0) {
shadowRadius = 0;
}
p.setShadowLayer(shadowRadius, 0, 0, shadowColor);
}
}
return true;
}
private void drawPoint(BinaryMapDataObject obj, RenderingRuleSearchRequest render, Canvas canvas, RenderingContext rc, TagValuePair pair, boolean renderText) {
if(render == null || pair == null){
return;
}
render.setInitialTagValueZoom(pair.tag, pair.value, rc.zoom, obj);
render.setIntFilter(render.ALL.R_TEXT_LENGTH, obj.getName().length());
render.search(RenderingRulesStorage.POINT_RULES);
String resId = render.getStringPropertyValue(render.ALL.R_ICON);
if(resId == null && !renderText){
return;
}
int len = obj.getPointsLength();
rc.visible++;
PointF ps = new PointF(0, 0);
for (int i = 0; i < len; i++) {
PointF p = calcPoint(obj, i, rc);
ps.x += p.x;
ps.y += p.y;
}
if(len > 1){
ps.x /= len;
ps.y /= len;
}
if(resId != null){
IconDrawInfo ico = new IconDrawInfo();
ico.x = ps.x;
ico.y = ps.y;
ico.iconOrder = render.getIntPropertyValue(render.ALL.R_ICON_ORDER, 100);
ico.iconSize = rc.getComplexValue(render, render.ALL.R_ICON_VISIBLE_SIZE, -1);
ico.shieldId = render.getStringPropertyValue(render.ALL.R_SHIELD);
ico.resId_1 = render.getStringPropertyValue(render.ALL.R_ICON__1);
ico.resId = resId;
ico.resId2 = render.getStringPropertyValue(render.ALL.R_ICON_2);
ico.resId3 = render.getStringPropertyValue(render.ALL.R_ICON_3);
ico.resId4 = render.getStringPropertyValue(render.ALL.R_ICON_4);
ico.resId5 = render.getStringPropertyValue(render.ALL.R_ICON_5);
rc.iconsToDraw.add(ico);
}
if (renderText) {
textRenderer.renderText(obj, render, rc, pair, ps.x, ps.y, null, null);
}
}
private void drawPolylineShadow(Canvas canvas, RenderingContext rc, Path path, int shadowColor, int shadowRadius) {
// blurred shadows
if (rc.shadowRenderingMode == 2 && shadowRadius > 0) {
// simply draw shadow? difference from option 3 ?
// paint.setColor(shadowRadius);
// paint.setColor(0xffffffff);
paint.setShadowLayer(shadowRadius, 0, 0, shadowColor);
canvas.drawPath(path, paint);
}
// option shadow = 3 with solid border
if (rc.shadowRenderingMode == 3 && shadowRadius > 0) {
paint.clearShadowLayer();
paint.setStrokeWidth(paint.getStrokeWidth() + shadowRadius * 2);
ColorFilter cf = new PorterDuffColorFilter(shadowColor, Mode.SRC_IN);
paint.setColorFilter(cf);
// paint.setColor(0xffbababa);
// paint.setColor(shadowColor);
canvas.drawPath(path, paint);
}
}
private void drawPolyline(BinaryMapDataObject obj, RenderingRuleSearchRequest render, Canvas canvas, RenderingContext rc, TagValuePair pair, int layer,
boolean drawOnlyShadow) {
if(render == null || pair == null){
return;
}
int length = obj.getPointsLength();
if(length < 2){
return;
}
render.setInitialTagValueZoom(pair.tag, pair.value, rc.zoom, obj);
render.setIntFilter(render.ALL.R_LAYER, layer);
boolean rendered = render.search(RenderingRulesStorage.LINE_RULES);
if(!rendered || !updatePaint(render, paint, 0, false, rc)){
return;
}
int oneway = 0;
if(rc.zoom >= 16 && "highway".equals(pair.tag) ){ //$NON-NLS-1$
if(obj.containsAdditionalType(obj.getMapIndex().onewayAttribute)) {
oneway = 1;
} else if(obj.containsAdditionalType(obj.getMapIndex().onewayReverseAttribute)){
oneway = -1;
}
}
rc.visible++;
Path path = null;
float xMid = 0;
float yMid = 0;
int middle = obj.getPointsLength() / 2;
PointF[] textPoints = null;
if (!drawOnlyShadow) {
textPoints = new PointF[length];
}
boolean intersect = false;
PointF prev = null;
for (int i = 0; i < length ; i++) {
PointF p = calcPoint(obj, i, rc);
if(textPoints != null) {
textPoints[i] = new PointF(p.x, p.y);
}
if (!intersect) {
if (p.x >= 0 && p.y >= 0 && p.x < rc.width && p.y < rc.height) {
intersect = true;
}
if (!intersect && prev != null) {
if ((p.x < 0 && prev.x < 0) || (p.y < 0 && prev.y < 0) || (p.x > rc.width && prev.x > rc.width)
|| (p.y > rc.height && prev.y > rc.height)) {
intersect = false;
} else {
intersect = true;
}
}
}
if (path == null) {
path = new Path();
path.moveTo(p.x, p.y);
} else {
if(i == middle){
xMid = p.x;
yMid = p.y;
}
path.lineTo(p.x, p.y);
}
prev = p;
}
if (!intersect) {
// System.err.println("Not intersect ");
// int[] ts = obj.getTypes();
// for(int i=0; i<ts.length; i++) {
// System.err.println(obj.getMapIndex().decodeType(ts[i]));
// }
return;
}
if (path != null) {
if(drawOnlyShadow) {
int shadowColor = render.getIntPropertyValue(render.ALL.R_SHADOW_COLOR);
int shadowRadius = (int) rc.getComplexValue(render, render.ALL.R_SHADOW_RADIUS);
if(shadowColor == 0) {
shadowColor = rc.shadowRenderingColor;
}
drawPolylineShadow(canvas, rc, path, shadowColor, shadowRadius);
} else {
boolean update = false;
if (updatePaint(render, paint, -3, false, rc)) {
update = true;
canvas.drawPath(path, paint);
}
if (updatePaint(render, paint, -2, false, rc)) {
update = true;
canvas.drawPath(path, paint);
}
if (updatePaint(render, paint, -1, false, rc)) {
update = true;
canvas.drawPath(path, paint);
}
if(update) {
updatePaint(render, paint, 0, false, rc);
}
canvas.drawPath(path, paint);
if (updatePaint(render, paint, 1, false, rc)) {
canvas.drawPath(path, paint);
}
if (updatePaint(render, paint, 2, false, rc)) {
canvas.drawPath(path, paint);
}
if (updatePaint(render, paint, 3, false, rc)) {
canvas.drawPath(path, paint);
}
if (updatePaint(render, paint, 4, false, rc)) {
canvas.drawPath(path, paint);
}
}
if(oneway != 0 && !drawOnlyShadow){
Paint[] paints = oneway == -1? getReverseOneWayPaints(rc) : getOneWayPaints(rc);
for (int i = 0; i < paints.length; i++) {
canvas.drawPath(path, paints[i]);
}
}
if (textPoints != null) {
textRenderer.renderText(obj, render, rc, pair, xMid, yMid, path, textPoints);
}
}
}
private static Paint oneWayPaint(){
Paint oneWay = new Paint();
oneWay.setStyle(Style.STROKE);
oneWay.setColor(0xff6c70d5);
oneWay.setAntiAlias(true);
return oneWay;
}
public Paint[] getReverseOneWayPaints(RenderingContext rc){
if(rc.reverseOneWay == null){
int rmin = (int)rc.getDensityValue(1);
if(rmin > 2) {
rmin = rmin / 2;
}
PathEffect arrowDashEffect1 = new DashPathEffect(new float[] { 0, 12, 10 * rmin, 152 }, 0);
PathEffect arrowDashEffect2 = new DashPathEffect(new float[] { 0, 12 + rmin, 9 * rmin, 152 }, 1);
PathEffect arrowDashEffect3 = new DashPathEffect(new float[] { 0, 12 + 2 * rmin, 2 * rmin, 152 + 6 * rmin }, 1);
PathEffect arrowDashEffect4 = new DashPathEffect(new float[] { 0, 12 + 3 * rmin, 1 * rmin, 152 + 6 * rmin }, 1);
rc.reverseOneWay = new Paint[4];
rc.reverseOneWay[0] = oneWayPaint();
rc.reverseOneWay[0].setStrokeWidth(rmin * 2);
rc.reverseOneWay[0].setPathEffect(arrowDashEffect1);
rc.reverseOneWay[1] = oneWayPaint();
rc.reverseOneWay[1].setStrokeWidth(rmin);
rc.reverseOneWay[1].setPathEffect(arrowDashEffect2);
rc.reverseOneWay[2] = oneWayPaint();
rc.reverseOneWay[2].setStrokeWidth(rmin * 3);
rc.reverseOneWay[2].setPathEffect(arrowDashEffect3);
rc.reverseOneWay[3] = oneWayPaint();
rc.reverseOneWay[3].setStrokeWidth(rmin * 4);
rc.reverseOneWay[3].setPathEffect(arrowDashEffect4);
}
return rc.reverseOneWay;
}
public Paint[] getOneWayPaints(RenderingContext rc){
if(rc.oneWay == null){
float rmin = rc.getDensityValue(1);
if(rmin > 1) {
rmin = rmin * 2 / 3;
}
PathEffect arrowDashEffect1 = new DashPathEffect(new float[] { 0, 12, 10 * rmin, 152 }, 0);
PathEffect arrowDashEffect2 = new DashPathEffect(new float[] { 0, 12, 9 * rmin, 152 + rmin }, 1);
PathEffect arrowDashEffect3 = new DashPathEffect(new float[] { 0, 12 + 6 * rmin, 2 * rmin, 152 + 2 * rmin}, 1);
PathEffect arrowDashEffect4 = new DashPathEffect(new float[] { 0, 12 + 6 * rmin, 1 * rmin, 152 + 3 * rmin}, 1);
rc.oneWay = new Paint[4];
rc.oneWay[0] = oneWayPaint();
rc.oneWay[0].setStrokeWidth(rmin);
rc.oneWay[0].setPathEffect(arrowDashEffect1);
rc.oneWay[1] = oneWayPaint();
rc.oneWay[1].setStrokeWidth(rmin * 2);
rc.oneWay[1].setPathEffect(arrowDashEffect2);
rc.oneWay[2] = oneWayPaint();
rc.oneWay[2].setStrokeWidth(rmin * 3);
rc.oneWay[2].setPathEffect(arrowDashEffect3);
rc.oneWay[3] = oneWayPaint();
rc.oneWay[3].setStrokeWidth(rmin * 4);
rc.oneWay[3].setPathEffect(arrowDashEffect4);
}
return rc.oneWay;
}
}