package net.osmand.plus.views; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Paint.Style; 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; public class MapTextLayer extends OsmandMapLayer { private Map<OsmandMapLayer, Collection<?>> textObjects = new LinkedHashMap<>(); public static final int TEXT_WRAP = 15; public static final int TEXT_LINES = 3; private Paint paintTextIcon; private OsmandMapTileView view; private boolean alwaysVisible; public interface MapTextProvider<T> { LatLon getTextLocation(T o); int getTextShift(T o, RotatedTileBox rb); String getText(T o); } public void putData(OsmandMapLayer ml, Collection<?> objects) { if(objects == null || objects.isEmpty()) { textObjects.remove(ml); } else { if(ml instanceof MapTextProvider) { textObjects.put(ml, objects); } else { throw new IllegalArgumentException(); } } } public boolean isAlwaysVisible() { return alwaysVisible; } public void setAlwaysVisible(boolean alwaysVisible) { this.alwaysVisible = alwaysVisible; } public boolean isVisible() { return view.getSettings().SHOW_POI_LABEL.get() || isAlwaysVisible(); } @SuppressWarnings("unchecked") @Override public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { if (!isVisible()) { return; } TIntHashSet set = new TIntHashSet(); for (OsmandMapLayer l : textObjects.keySet()) { if (view.isLayerVisible(l)) { for (Object o : textObjects.get(l)) { LatLon location = ((MapTextProvider) l).getTextLocation(o); int x = (int) tileBox.getPixXFromLatLon(location.getLatitude(), location .getLongitude()); int y = (int) tileBox.getPixYFromLatLon(location.getLatitude(), location .getLongitude()); int tx = tileBox.getPixXFromLonNoRot(location.getLongitude()); int ty = tileBox.getPixYFromLatNoRot(location.getLatitude()); String name = ((MapTextProvider) l).getText(o); if (name != null && name.length() > 0) { 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) { // drawWrappedText(canvas, "...", paintTextIcon.getTextSize(), x, y + r + 2 + // paintTextIcon.getTextSize() / 2, 1); } else { int r = ((MapTextProvider) l).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) { 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.isLetterOrDigit(text.charAt(pos))){ lastSpace = pos; } pos++; } if(lastSpace == -1 || (pos == end)){ drawShadowText(cv, text.substring(start, pos), x, y + line * (textSize + 2)); start = pos; } else { String subtext = text.substring(start, lastSpace); if (line + 1 == lines) { subtext += ".."; } drawShadowText(cv, subtext, x, y + line * (textSize + 2)); start = lastSpace + 1; limit += (start - pos) - 1; } line++; } } else { drawShadowText(cv, text, x, y); } } private void drawShadowText(Canvas cv, String text, float centerX, float centerY) { int c = paintTextIcon.getColor(); paintTextIcon.setStyle(Style.STROKE); paintTextIcon.setColor(Color.WHITE); paintTextIcon.setStrokeWidth(2); cv.drawText(text, centerX, centerY, paintTextIcon); // reset paintTextIcon.setStrokeWidth(2); paintTextIcon.setStyle(Style.FILL); paintTextIcon.setColor(c); cv.drawText(text, centerX, centerY, paintTextIcon); } @Override public void initLayer(OsmandMapTileView v) { this.view = v; paintTextIcon = new Paint(); paintTextIcon.setTextSize(13 * v.getDensity()); paintTextIcon.setTextAlign(Align.CENTER); paintTextIcon.setAntiAlias(true); Map<OsmandMapLayer, Collection<?>> textObjectsLoc = new TreeMap<OsmandMapLayer, Collection<?>>(new Comparator<OsmandMapLayer>() { @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; } }