package net.osmand.plus.render;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.hash.TFloatObjectHashMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.osmand.LogUtil;
import net.osmand.binary.BinaryMapDataObject;
import net.osmand.binary.BinaryMapIndexReader.TagValuePair;
import net.osmand.data.preparation.MapTileDownloader.IMapDownloaderCallback;
import net.osmand.osm.MapRenderingTypes;
import net.osmand.osm.MultyPolygon;
import net.sf.junidecode.Junidecode;
import org.apache.commons.logging.Log;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.graphics.Paint.Align;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Style;
import android.graphics.Shader.TileMode;
import android.text.TextPaint;
import android.util.DisplayMetrics;
import android.util.FloatMath;
import android.view.WindowManager;
public class OsmandRenderer {
private static final Log log = LogUtil.getLog(OsmandRenderer.class);
private final int clFillScreen = Color.rgb(241, 238, 232);
private TextPaint paintText;
private Paint paint;
private Paint paintFillEmpty;
private Paint paintIcon;
public static final int TILE_SIZE = 256;
private Map<String, PathEffect> dashEffect = new LinkedHashMap<String, PathEffect>();
private Map<Integer, Shader> shaders = new LinkedHashMap<Integer, Shader>();
private Map<Integer, Bitmap> cachedIcons = new LinkedHashMap<Integer, Bitmap>();
private final Context context;
private DisplayMetrics dm;
private static class TextDrawInfo {
public TextDrawInfo(String text){
this.text = text;
}
public void fillProperties(RenderingContext rc, float centerX, float centerY){
this.centerX = centerX + rc.textDx;
this.centerY = centerY + rc.textDy;
textColor = rc.textColor;
textSize = rc.textSize;
textShadow = (int) rc.textHaloRadius;
textWrap = rc.textWrapWidth;
bold = rc.textBold;
minDistance = rc.textMinDistance;
shieldRes = rc.textShield;
}
String text = null;
Path drawOnPath = null;
float vOffset = 0;
float centerX = 0;
float pathRotate = 0;
float centerY = 0;
float textSize = 0;
float minDistance = 0;
int textColor = Color.BLACK;
int textShadow = 0;
int textWrap = 0;
boolean bold = false;
int shieldRes = 0;
}
private static class IconDrawInfo {
float x = 0;
float y = 0;
int resId;
}
/*package*/ static class RenderingContext {
public boolean interrupted = false;
List<TextDrawInfo> textToDraw = new ArrayList<TextDrawInfo>();
List<IconDrawInfo> iconsToDraw = new ArrayList<IconDrawInfo>();
float leftX;
float topY;
int width;
int height;
int zoom;
float rotate;
float tileDivisor;
// debug purpose
int pointCount = 0;
int pointInsideCount = 0;
int visible = 0;
int allObjects = 0;
// use to calculate points
PointF tempPoint = new PointF();
float cosRotateTileSize;
float sinRotateTileSize;
// These properties are used for rendering one object
// polyline props
boolean showTextOnPath = false;
String showAnotherText = null;
float textSize = 0;
int textColor = 0;
int textMinDistance = 0;
int textWrapWidth = 0;
float textDx = 0;
float textDy = 0;
float textHaloRadius = 0;
boolean textBold;
int textShield = 0;
RenderingPaintProperties main = new RenderingPaintProperties();
RenderingPaintProperties second = new RenderingPaintProperties();
RenderingPaintProperties third = new RenderingPaintProperties();
RenderingPaintProperties[] adds = null;
public void clearText() {
showAnotherText = null;
showTextOnPath = false;
textSize = 0;
textColor = 0;
textMinDistance = 0;
textWrapWidth = 0;
textDx = 0;
textDy = 0;
textHaloRadius = 0;
textBold = false;
textShield = 0;
}
}
/* package*/ static class RenderingPaintProperties {
int color;
float strokeWidth;
int shadowLayer;
int shadowColor;
boolean fillArea;
PathEffect pathEffect;
Shader shader;
Cap cap;
public void emptyLine(){
color = 0;
strokeWidth = 0;
cap = Cap.BUTT;
pathEffect = null;
fillArea = false;
shader = null;
shadowColor = 0;
shadowLayer = 0;
}
public void updatePaint(Paint p){
p.setStyle(fillArea ? Style.FILL_AND_STROKE : Style.STROKE);
p.setColor(color);
p.setShader(shader);
if(shadowColor == 0){
shadowLayer = 0;
}
p.setShadowLayer(shadowLayer, 0, 0, shadowColor);
p.setStrokeWidth(strokeWidth);
p.setStrokeCap(cap);
if (!fillArea) {
p.setPathEffect(pathEffect);
}
}
public void emptyArea(){
color = 0;
strokeWidth = 0;
cap = Cap.BUTT;
fillArea = false;
shader = null;
pathEffect = null;
shadowColor = 0;
shadowLayer = 0;
}
}
public OsmandRenderer(Context context) {
this.context = context;
paintIcon = new Paint();
paintIcon.setStyle(Style.STROKE);
paintText = new TextPaint();
paintText.setStyle(Style.FILL);
paintText.setColor(Color.BLACK);
paintText.setTextAlign(Align.CENTER);
paintText.setTypeface(Typeface.create("Droid Serif", Typeface.NORMAL)); //$NON-NLS-1$
paintText.setAntiAlias(true);
paint = new Paint();
paint.setAntiAlias(true);
paintFillEmpty = new Paint();
paintFillEmpty.setStyle(Style.FILL);
paintFillEmpty.setColor(clFillScreen);
dm = new DisplayMetrics();
WindowManager wmgr = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wmgr.getDefaultDisplay().getMetrics(dm);
}
public PathEffect getDashEffect(String dashes){
if(!dashEffect.containsKey(dashes)){
String[] ds = dashes.split("_"); //$NON-NLS-1$
float[] f = new float[ds.length];
for(int i=0; i<ds.length; i++){
f[i] = Float.parseFloat(ds[i]);
}
dashEffect.put(dashes, new DashPathEffect(f, 0));
}
return dashEffect.get(dashes);
}
public Shader getShader(int resId){
if(shaders.get(resId) == null){
Shader sh = new BitmapShader(
BitmapFactory.decodeResource(context.getResources(), resId), TileMode.REPEAT, TileMode.REPEAT);
shaders.put(resId, sh);
}
return shaders.get(resId);
}
private void put(TFloatObjectHashMap<TIntArrayList> map, Float k, int v, int init){
if(!map.containsKey(k)){
map.put(k, new TIntArrayList());
}
map.get(k).add(v);
}
public Bitmap generateNewBitmap(RenderingContext rc, List<BinaryMapDataObject> objects, Bitmap bmp, boolean useEnglishNames,
BaseOsmandRender renderer,
List<IMapDownloaderCallback> notifyList) {
long now = System.currentTimeMillis();
// fill area
Canvas cv = new Canvas(bmp);
if(renderer != null){
int dc = renderer.getDefaultColor();
if(dc != 0){
paintFillEmpty.setColor(dc);
}
}
cv.drawRect(0, 0, bmp.getWidth(), bmp.getHeight(), paintFillEmpty);
// put in order map
int sz = objects.size();
int init = sz / 4;
TFloatObjectHashMap<TIntArrayList> orderMap = new TFloatObjectHashMap<TIntArrayList>();
if (renderer != null) {
for (int i = 0; i < sz; i++) {
BinaryMapDataObject o = objects.get(i);
int sh = i << 8;
if (o instanceof MultyPolygon) {
int mask = MapRenderingTypes.MULTY_POLYGON_TYPE;
int layer = ((MultyPolygon) o).getLayer();
put(orderMap, renderer.getObjectOrder(((MultyPolygon) o).getTag(), ((MultyPolygon) o).getValue(),
mask, layer), sh, init);
} else {
for (int j = 0; j < o.getTypes().length; j++) {
// put(orderMap, BinaryMapDataObject.getOrder(o.getTypes()[j]), sh + j, init);
int wholeType = o.getTypes()[j];
int mask = wholeType & 3;
int layer = 0;
if (mask != 1) {
layer = MapRenderingTypes.getNegativeWayLayer(wholeType);
}
TagValuePair pair = o.getMapIndex().decodeType(MapRenderingTypes.getMainObjectType(wholeType),
MapRenderingTypes.getObjectSubType(wholeType));
if (pair != null) {
put(orderMap, renderer.getObjectOrder(pair.tag, pair.value, mask, layer), sh + j, init);
}
}
}
if (rc.interrupted) {
return null;
}
}
}
if (objects != null && !objects.isEmpty() && rc.width > 0 && rc.height > 0) {
// init rendering context
rc.tileDivisor = (int) (1 << (31 - rc.zoom));
rc.cosRotateTileSize = FloatMath.cos((float) Math.toRadians(rc.rotate)) * TILE_SIZE;
rc.sinRotateTileSize = FloatMath.sin((float) Math.toRadians(rc.rotate)) * TILE_SIZE;
float[] keys = orderMap.keys();
Arrays.sort(keys);
int objCount = 0;
for (int k = 0; k < keys.length; k++) {
TIntArrayList list = orderMap.get(keys[k]);
for (int j = 0; j < list.size(); j++) {
int i = list.get(j);
int ind = i >> 8;
int l = i & 0xff;
BinaryMapDataObject obj = objects.get(ind);
// show text only for main type
drawObj(obj, renderer, cv, rc, l, l == 0);
objCount++;
}
if(objCount > 25){
notifyListeners(notifyList);
objCount = 0;
}
if(rc.interrupted){
return null;
}
}
notifyListeners(notifyList);
int skewConstant = (int) (16 * dm.density);
int iconsW = rc.width / skewConstant ;
int iconsH = rc.height / skewConstant;
int[] alreadyDrawnIcons = new int[iconsW * iconsH / 32];
for(IconDrawInfo icon : rc.iconsToDraw){
if (icon.resId != 0) {
if (cachedIcons.get(icon.resId) == null) {
cachedIcons.put(icon.resId, UnscaledBitmapLoader.loadFromResource(context.getResources(), icon.resId, null, dm));
}
Bitmap ico = cachedIcons.get(icon.resId);
if (ico != null) {
if (icon.y >= 0 && icon.y < rc.height && icon.x >= 0 && icon.x < rc.width) {
int z = (((int) icon.x / skewConstant) + ((int) icon.y / skewConstant) * iconsW);
int i = z / 32;
if (i >= alreadyDrawnIcons.length) {
continue;
}
int ind = alreadyDrawnIcons[i];
int b = z % 32;
// check bit b if it is set
if (((ind >> b) & 1) == 0) {
alreadyDrawnIcons[i] = ind | (1 << b);
cv.drawBitmap(ico, icon.x - ico.getWidth() / 2, icon.y - ico.getHeight() / 2, paintIcon);
}
}
}
}
if(rc.interrupted){
return null;
}
}
notifyListeners(notifyList);
drawTextOverCanvas(rc, cv, useEnglishNames);
long time = System.currentTimeMillis() - now;
log.info(String.format("Rendering has been done in %s ms. (%s points, %s points inside, %s visile from %s)",//$NON-NLS-1$
time, rc.pointCount, rc.pointInsideCount, rc.visible, rc.allObjects));
}
return bmp;
}
private void notifyListeners(List<IMapDownloaderCallback> notifyList) {
if (notifyList != null) {
for (IMapDownloaderCallback c : notifyList) {
c.tileDownloaded(null);
}
}
}
private final static boolean findAllTextIntersections = true;
public void drawTextOverCanvas(RenderingContext rc, Canvas cv, boolean useEnglishNames) {
List<RectF> boundsNotPathIntersect = new ArrayList<RectF>();
List<RectF> boundsPathIntersect = new ArrayList<RectF>();
int size = rc.textToDraw.size();
Comparator<RectF> c = new Comparator<RectF>(){
@Override
public int compare(RectF object1, RectF object2) {
return Float.compare(object1.left, object2.left);
}
};
next: for (int i = 0; i < size; i++) {
TextDrawInfo text = rc.textToDraw.get(i);
if(text.text != null){
int d = text.text.indexOf(MapRenderingTypes.DELIM_CHAR);
// not used now functionality
// possibly it will be used specifying english names after that character
if(d > 0){
text.text = text.text.substring(0, d);
}
if(useEnglishNames){
text.text = Junidecode.unidecode(text.text);
}
RectF bounds = new RectF();
paintText.setTextSize(text.textSize * dm.density);
paintText.setFakeBoldText(text.bold);
float mes = paintText.measureText(text.text);
if(text.drawOnPath == null ||
(text.pathRotate > 45 && text.pathRotate < 135) || (text.pathRotate > 225 && text.pathRotate < 315)){
bounds.set(text.centerX - mes / 2, text.centerY - 3 * text.textSize / 2 ,
text.centerX + mes / 2 , text.centerY + 3 * text.textSize / 2 );
} else {
bounds.set(text.centerX - 3 * text.textSize , text.centerY - mes,
text.centerX + 3 * text.textSize , text.centerY + mes );
}
if(text.minDistance > 0){
bounds.set(bounds.left - text.minDistance / 2, bounds.top - text.minDistance / 2,
bounds.right + text.minDistance / 2, bounds.bottom + text.minDistance / 2);
}
List<RectF> boundsIntersect = text.drawOnPath == null || findAllTextIntersections?
boundsNotPathIntersect : boundsPathIntersect;
if(boundsIntersect.isEmpty()){
boundsIntersect.add(bounds);
} else {
final int diff = (int) (3 * dm.density);
final int diff2 = (int) (15 * dm.density);
// implement binary search
int index = Collections.binarySearch(boundsIntersect, bounds, c);
if (index < 0) {
index = -(index + 1);
}
// find sublist that is appropriate
int e = index;
while (e < boundsIntersect.size()) {
if (boundsIntersect.get(e).left < bounds.right ) {
e++;
} else {
break;
}
}
int st = index - 1;
while (st >= 0) {
// that's not exact algorithm that replace comparison rect with each other
// because of that comparison that is not obvious
// (we store array sorted by left boundary, not by right) - that's euristic
if (boundsIntersect.get(st).right > bounds.left ) {
st--;
} else {
break;
}
}
if (st < 0) {
st = 0;
}
for (int j = st; j < e; j++) {
RectF b = boundsIntersect.get(j);
float x = Math.min(bounds.right, b.right) - Math.max(b.left, bounds.left);
float y = Math.min(bounds.bottom, b.bottom) - Math.max(b.top, bounds.top);
if ((x > diff && y > diff2) || (x > diff2 && y > diff)) {
continue next;
}
}
// store in list sorted by left boundary
if(text.minDistance > 0){
bounds.set(bounds.left + text.minDistance / 2, bounds.top + text.minDistance / 2,
bounds.right - text.minDistance / 2, bounds.bottom - text.minDistance / 2);
}
boundsIntersect.add(index, bounds);
}
// Shadow layer
// paintText.setShadowLayer(text.textShadow, 0, 0, Color.WHITE);
// if(text.textShadow > 0){
// paintText.setColor(Color.WHITE);
// paintText.setTextSize(text.textSize + text.textShadow * 2);
// if(text.drawOnPath != null){
// cv.drawTextOnPath(text.text, text.drawOnPath, 0, text.vOffset, paintText);
// } else {
// cv.drawText(text.text, text.centerX, text.centerY, paintText);
// }
// paintText.setTextSize(text.textSize);
// }
paintText.setColor(text.textColor);
if(text.drawOnPath != null){
cv.drawTextOnPath(text.text, text.drawOnPath, 0, text.vOffset, paintText);
} else {
if(text.textWrap == 0){
// set maximum for all text
text.textWrap = 40;
}
if(text.shieldRes != 0){
if(cachedIcons.get(text.shieldRes) == null){
cachedIcons.put(text.shieldRes, BitmapFactory.decodeResource(context.getResources(), text.shieldRes));
}
Bitmap ico = cachedIcons.get(text.shieldRes);
if (ico != null) {
cv.drawBitmap(ico, text.centerX - ico.getWidth() / 2 - 0.5f * dm.density,
text.centerY - text.textSize / 2 - 6.5f * dm.density,
paintIcon);
}
}
if(text.text.length() > text.textWrap){
int start = 0;
int end = text.text.length();
int lastSpace = -1;
int line = 0;
int pos = 0;
int limit = 0;
while(pos < end){
lastSpace = -1;
limit += text.textWrap;
while(pos < limit && pos < end){
if(!Character.isLetterOrDigit(text.text.charAt(pos))){
lastSpace = pos;
}
pos++;
}
if(lastSpace == -1){
cv.drawText(text.text.substring(start, pos),
text.centerX, text.centerY + line * (text.textSize + 2), paintText);
start = pos;
} else {
cv.drawText(text.text.substring(start, lastSpace),
text.centerX, text.centerY + line * (text.textSize + 2), paintText);
start = lastSpace + 1;
limit += (start - pos) - 1;
}
line++;
}
} else {
cv.drawText(text.text, text.centerX, text.centerY, paintText);
}
}
}
}
}
protected void drawObj(BinaryMapDataObject obj, BaseOsmandRender render, Canvas canvas, RenderingContext rc, int l, boolean renderText) {
rc.allObjects++;
if (obj instanceof MultyPolygon) {
drawMultiPolygon(obj, render,canvas, rc);
} else {
int mainType = obj.getTypes()[l];
int t = mainType & 3;
int type = MapRenderingTypes.getMainObjectType(mainType);
int subtype = MapRenderingTypes.getObjectSubType(mainType);
TagValuePair pair = obj.getMapIndex().decodeType(type, subtype);
if (t == MapRenderingTypes.POINT_TYPE) {
drawPoint(obj, render, canvas, rc, pair, renderText);
} else if (t == MapRenderingTypes.POLYLINE_TYPE) {
int layer = MapRenderingTypes.getNegativeWayLayer(mainType);
drawPolyline(obj, render, canvas, rc, pair, layer);
} else if (t == MapRenderingTypes.POLYGON_TYPE) {
drawPolygon(obj, render, canvas, rc, pair);
} else {
if (t == MapRenderingTypes.MULTY_POLYGON_TYPE && !(obj instanceof MultyPolygon)) {
// log this situation
return;
}
}
}
}
private PointF calcPoint(BinaryMapDataObject o, int ind, RenderingContext rc){
rc.pointCount ++;
float tx = o.getPoint31XTile(ind) / rc.tileDivisor;
float ty = o.getPoint31YTile(ind) / rc.tileDivisor;
float dTileX = tx - rc.leftX;
float dTileY = ty - rc.topY;
float x = rc.cosRotateTileSize * dTileX - rc.sinRotateTileSize * dTileY;
float y = 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 calcMultiPolygonPoint(MultyPolygon o, int i, int b, RenderingContext rc){
rc.pointCount ++;
float tx = o.getPoint31XTile(i, b)/ rc.tileDivisor;
float ty = o.getPoint31YTile(i, b) / rc.tileDivisor;
float dTileX = tx - rc.leftX;
float dTileY = ty - rc.topY;
float x = rc.cosRotateTileSize * dTileX - rc.sinRotateTileSize * dTileY;
float y = 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;
}
public void clearCachedResources(){
cachedIcons.clear();
shaders.clear();
}
private void drawMultiPolygon(BinaryMapDataObject obj, BaseOsmandRender render, Canvas canvas, RenderingContext rc) {
String tag = ((MultyPolygon)obj).getTag();
String value = ((MultyPolygon)obj).getValue();
if(render == null || tag == null){
return;
}
rc.main.emptyArea();
rc.second.emptyLine();
rc.main.color = Color.rgb(245, 245, 245);
boolean rendered = render.renderPolygon(tag, value, rc.zoom, rc, this);
if(!rendered){
return;
}
rc.visible++;
Path path = new Path();
for (int i = 0; i < ((MultyPolygon) obj).getBoundsCount(); i++) {
int cnt = ((MultyPolygon) obj).getBoundPointsCount(i);
float xText = 0;
float yText = 0;
for (int j = 0; j < cnt; j++) {
PointF p = calcMultiPolygonPoint((MultyPolygon) obj, j, i, rc);
xText += p.x;
yText += p.y;
if (j == 0) {
path.moveTo(p.x, p.y);
} else {
path.lineTo(p.x, p.y);
}
}
if (cnt > 0) {
String name = ((MultyPolygon) obj).getName(i);
if (name != null) {
rc.clearText();
name = render.renderObjectText(name, tag, value, rc, false);
if (rc.textSize > 0 && name != null) {
TextDrawInfo info = new TextDrawInfo(name);
info.fillProperties(rc, xText / cnt, yText / cnt);
rc.textToDraw.add(info);
}
}
}
}
rc.main.updatePaint(paint);
canvas.drawPath(path, paint);
// for test purpose
// rc.second.strokeWidth = 1.5f;
// rc.second.color = Color.BLACK;
if (rc.second.strokeWidth != 0) {
rc.second.updatePaint(paint);
canvas.drawPath(path, paint);
}
}
private void drawPolygon(BinaryMapDataObject obj, BaseOsmandRender render, Canvas canvas, RenderingContext rc, TagValuePair pair) {
if(render == null || pair == null){
return;
}
float xText = 0;
float yText = 0;
int zoom = rc.zoom;
Path path = null;
rc.main.emptyArea();
rc.second.emptyLine();
// rc.main.color = Color.rgb(245, 245, 245);
boolean rendered = render.renderPolygon(pair.tag, pair.value, zoom, rc, this);
if(!rendered){
return;
}
rc.visible++;
int len = obj.getPointsLength();
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);
}
}
if (path != null && len > 0) {
rc.main.updatePaint(paint);
canvas.drawPath(path, paint);
if (rc.second.strokeWidth != 0) {
rc.second.updatePaint(paint);
canvas.drawPath(path, paint);
}
String name = obj.getName();
if(name != null){
rc.clearText();
name = render.renderObjectText(name, pair.tag, pair.value, rc, false);
if (rc.textSize > 0 && name != null) {
xText /= len;
yText /= len;
TextDrawInfo info = new TextDrawInfo(name);
info.fillProperties(rc, xText, yText);
rc.textToDraw.add(info);
}
}
}
return;
}
private void drawPoint(BinaryMapDataObject obj, BaseOsmandRender render, Canvas canvas, RenderingContext rc, TagValuePair pair, boolean renderText) {
if(render == null || pair == null){
return;
}
Integer resId = render.getPointIcon(pair.tag, pair.value, rc.zoom);
String name = null;
if (renderText) {
name = obj.getName();
if (name != null) {
rc.clearText();
name = render.renderObjectText(name, pair.tag, pair.value, rc, false);
}
}
if((resId == null || resId == 0) && name == null){
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 && resId != 0){
IconDrawInfo ico = new IconDrawInfo();
ico.x = ps.x;
ico.y = ps.y;
ico.resId = resId;
rc.iconsToDraw.add(ico);
}
if (name != null && rc.textSize > 0) {
TextDrawInfo info = new TextDrawInfo(name);
info.fillProperties(rc, ps.x, ps.y);
rc.textToDraw.add(info);
}
}
private void drawPolyline(BinaryMapDataObject obj, BaseOsmandRender render, Canvas canvas, RenderingContext rc, TagValuePair pair, int layer) {
if(render == null || pair == null){
return;
}
int length = obj.getPointsLength();
if(length < 2){
return;
}
rc.main.emptyLine();
rc.second.emptyLine();
rc.third.emptyLine();
rc.adds = null;
boolean res = render.renderPolyline(pair.tag, pair.value, rc.zoom, rc, this, layer);
if(rc.main.strokeWidth == 0 || !res){
return;
}
if(rc.zoom >= 16 && "highway".equals(pair.tag) && MapRenderingTypes.isOneWayWay(obj.getHighwayAttributes())){ //$NON-NLS-1$
rc.adds = getOneWayProperties();
}
rc.visible++;
Path path = null;
float pathRotate = 0;
float xLength = 0;
float yLength = 0;
boolean inverse = false;
float xPrev = 0;
float yPrev = 0;
float xMid = 0;
float yMid = 0;
PointF middlePoint = new PointF();
int middle = obj.getPointsLength() / 2;
for (int i = 0; i < length ; i++) {
PointF p = calcPoint(obj, i, rc);
if(i == 0 || i == length -1){
xMid += p.x;
yMid += p.y;
}
if (path == null) {
path = new Path();
path.moveTo(p.x, p.y);
} else {
xLength += p.x - xPrev; // not abs
yLength += p.y - yPrev; // not abs
if(i == middle){
middlePoint.set(p.x, p.y);
double rot = - Math.atan2(p.x - xPrev, p.y - yPrev) * 180 / Math.PI;
if (rot < 0) {
rot += 360;
}
if (rot < 180) {
rot += 180;
inverse = true;
}
pathRotate = (float) rot;
}
path.lineTo(p.x, p.y);
}
xPrev = p.x;
yPrev = p.y;
}
if (path != null) {
rc.main.updatePaint(paint);
canvas.drawPath(path, paint);
if (rc.second.strokeWidth != 0) {
rc.second.updatePaint(paint);
canvas.drawPath(path, paint);
if (rc.third.strokeWidth != 0) {
rc.third.updatePaint(paint);
canvas.drawPath(path, paint);
}
}
if (rc.adds != null) {
for (int i = 0; i < rc.adds.length; i++) {
rc.adds[i].updatePaint(paint);
canvas.drawPath(path, paint);
}
}
if (obj.getName() != null && obj.getName().length() > 0) {
String name = obj.getName();
String ref = null;
if(name.charAt(0) == MapRenderingTypes.REF_CHAR){
ref = name.substring(1);
name = ""; //$NON-NLS-1$
for(int k = 0; k < ref.length(); k++){
if(ref.charAt(k) == MapRenderingTypes.REF_CHAR){
if(k < ref.length() - 1){
name = ref.substring(k + 1);
}
ref = ref.substring(0, k);
break;
}
}
}
if(ref != null && ref.trim().length() > 0){
rc.clearText();
ref = render.renderObjectText(ref, pair.tag, pair.value, rc, true);
TextDrawInfo text = new TextDrawInfo(ref);
if(!rc.showTextOnPath){
text.fillProperties(rc, middlePoint.x, middlePoint.y);
} else {
// TODO
}
rc.textToDraw.add(text);
}
if(name != null && name.trim().length() > 0){
rc.clearText();
name = render.renderObjectText(name, pair.tag, pair.value, rc, false);
if (rc.textSize > 0) {
TextDrawInfo text = new TextDrawInfo(name);
if (!rc.showTextOnPath) {
text.fillProperties(rc, middlePoint.x, middlePoint.y);
rc.textToDraw.add(text);
} else {
if (paintText.measureText(obj.getName()) < Math.max(Math.abs(xLength), Math.abs(yLength))) {
if (inverse) {
path.rewind();
boolean st = true;
for (int i = obj.getPointsLength() - 1; i >= 0; i--) {
PointF p = calcPoint(obj, i, rc);
if (st) {
st = false;
path.moveTo(p.x, p.y);
} else {
path.lineTo(p.x, p.y);
}
}
}
text.fillProperties(rc, xMid / 2, yMid / 2);
text.pathRotate = pathRotate;
text.drawOnPath = path;
text.vOffset = rc.main.strokeWidth / 2 - 1;
rc.textToDraw.add(text);
}
}
}
}
}
}
}
private static RenderingPaintProperties[] oneWay = null;
public static RenderingPaintProperties[] getOneWayProperties(){
if(oneWay == null){
PathEffect arrowDashEffect1 = new DashPathEffect(new float[] { 0, 12, 10, 152 }, 0);
PathEffect arrowDashEffect2 = new DashPathEffect(new float[] { 0, 12, 9, 153 }, 1);
PathEffect arrowDashEffect3 = new DashPathEffect(new float[] { 0, 18, 2, 154 }, 1);
PathEffect arrowDashEffect4 = new DashPathEffect(new float[] { 0, 18, 1, 155 }, 1);
oneWay = new RenderingPaintProperties[4];
oneWay[0] = new RenderingPaintProperties();
oneWay[0].emptyLine();
oneWay[0].color = 0xff6c70d5;
oneWay[0].strokeWidth = 1;
oneWay[0].pathEffect = arrowDashEffect1;
oneWay[1] = new RenderingPaintProperties();
oneWay[1].emptyLine();
oneWay[1].color = 0xff6c70d5;
oneWay[1].strokeWidth = 2;
oneWay[1].pathEffect = arrowDashEffect2;
oneWay[2] = new RenderingPaintProperties();
oneWay[2].emptyLine();
oneWay[2].color = 0xff6c70d5;
oneWay[2].strokeWidth = 3;
oneWay[2].pathEffect = arrowDashEffect3;
oneWay[3] = new RenderingPaintProperties();
oneWay[3].emptyLine();
oneWay[3].color = 0xff6c70d5;
oneWay[3].strokeWidth = 4;
oneWay[3].pathEffect = arrowDashEffect4;
}
return oneWay;
}
}