Changeset 11748 in josm for trunk


Ignore:
Timestamp:
2017-03-20T17:47:47+01:00 (7 years ago)
Author:
michael2402
Message:

Move label / icon placement code to new package. Unify the handling of text label positioning (path / center)

Location:
trunk
Files:
6 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java

    r11746 r11748  
    2929import java.util.Collection;
    3030import java.util.Collections;
    31 import java.util.Comparator;
    3231import java.util.HashMap;
    3332import java.util.Iterator;
     
    4140import java.util.function.Consumer;
    4241import java.util.function.Supplier;
    43 import java.util.stream.Collectors;
    4442
    4543import javax.swing.AbstractButton;
     
    6664import org.openstreetmap.josm.gui.NavigatableComponent;
    6765import org.openstreetmap.josm.gui.draw.MapViewPath;
     66import org.openstreetmap.josm.gui.draw.MapViewPositionAndRotation;
    6867import org.openstreetmap.josm.gui.mappaint.ElemStyles;
    6968import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
     
    7675import org.openstreetmap.josm.gui.mappaint.styleelement.MapImage;
    7776import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement;
    78 import org.openstreetmap.josm.gui.mappaint.styleelement.PositionForAreaStrategy;
    7977import org.openstreetmap.josm.gui.mappaint.styleelement.RepeatImageElement.LineImageAlignment;
    8078import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
     
    8280import org.openstreetmap.josm.gui.mappaint.styleelement.TextElement;
    8381import org.openstreetmap.josm.gui.mappaint.styleelement.TextLabel;
     82import org.openstreetmap.josm.gui.mappaint.styleelement.placement.PositionForAreaStrategy;
    8483import org.openstreetmap.josm.tools.CompositeList;
    8584import org.openstreetmap.josm.tools.Geometry;
     
    340339
    341340    /**
    342      * Displays text at specified position including its halo, if applicable.
    343      *
    344      * @param gv Text's glyphs to display. If {@code null}, use text from {@code s} instead.
    345      * @param s text to display if {@code gv} is {@code null}
    346      * @param x X position
    347      * @param y Y position
    348      * @param disabled {@code true} if element is disabled (filtered out)
    349      * @param text text style to use
    350      */
    351     private void displayText(GlyphVector gv, String s, int x, int y, boolean disabled, TextLabel text) {
    352         if (gv == null && s.isEmpty()) return;
    353         if (isInactiveMode || disabled) {
    354             g.setColor(inactiveColor);
    355             if (gv != null) {
    356                 g.drawGlyphVector(gv, x, y);
    357             } else {
    358                 g.setFont(text.font);
    359                 g.drawString(s, x, y);
    360             }
    361         } else if (text.haloRadius != null) {
    362             g.setStroke(new BasicStroke(2*text.haloRadius, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
    363             g.setColor(text.haloColor);
    364             Shape textOutline;
    365             if (gv == null) {
    366                 FontRenderContext frc = g.getFontRenderContext();
    367                 TextLayout tl = new TextLayout(s, text.font, frc);
    368                 textOutline = tl.getOutline(AffineTransform.getTranslateInstance(x, y));
    369             } else {
    370                 textOutline = gv.getOutline(x, y);
    371             }
    372             g.draw(textOutline);
    373             g.setStroke(new BasicStroke());
    374             g.setColor(text.color);
    375             g.fill(textOutline);
    376         } else {
    377             g.setColor(text.color);
    378             if (gv != null) {
    379                 g.drawGlyphVector(gv, x, y);
    380             } else {
    381                 g.setFont(text.font);
    382                 g.drawString(s, x, y);
    383             }
    384         }
    385     }
    386 
    387     /**
    388341     * Worker function for drawing areas.
    389342     *
     
    400353     * polygons)
    401354     * @param disabled If this should be drawn with a special disabled style.
    402      * @param text The text to write on the area.
    403      */
    404     protected void drawArea(OsmPrimitive osm, Path2D.Double path, Color color,
     355     * @param text Ignored. Use {@link #drawText(OsmPrimitive, TextLabel)} instead.
     356     */
     357    protected void drawArea(OsmPrimitive osm, MapViewPath path, Color color,
    405358            MapImage fillImage, Float extent, Path2D.Double pfClip, boolean disabled, TextLabel text) {
    406359        if (!isOutlineOnly && color.getAlpha() != 0) {
    407             Shape area = path.createTransformedShape(mapState.getAffineTransform());
     360            Shape area = path;
    408361            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
    409362            if (fillImage == null) {
     
    450403            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasing);
    451404        }
    452 
    453         drawAreaText(osm, text, path);
    454     }
    455 
    456     private void drawAreaText(OsmPrimitive osm, TextLabel text, Path2D.Double path) {
    457         if (text != null && isShowNames() && isAreaVisible(path)) {
    458             // abort if we can't compose the label to be rendered
    459             if (text.labelCompositionStrategy == null) return;
    460             String name = text.labelCompositionStrategy.compose(osm);
    461             if (name == null || name.isEmpty()) return;
    462 
    463             Shape area = path.createTransformedShape(mapState.getAffineTransform());
    464             FontMetrics fontMetrics = g.getFontMetrics(orderFont); // if slow, use cache
    465             Rectangle2D nb = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font)
    466 
    467             Rectangle2D centeredNBounds = text.getLabelPositionStrategy().findLabelPlacement(area, nb);
    468             if (centeredNBounds != null) {
    469                 Font defaultFont = g.getFont();
    470                 int x = (int) (centeredNBounds.getMinX() - nb.getMinX());
    471                 int y = (int) (centeredNBounds.getMinY() - nb.getMinY());
    472                 displayText(null, name, x, y, osm.isDisabled(), text);
    473                 g.setFont(defaultFont);
    474             } else if (Main.isTraceEnabled()) {
    475                 Main.trace("Couldn't find a correct label placement for "+osm+" / "+name);
    476             }
    477         }
    478405    }
    479406
     
    495422        if (!r.isDisabled() && !multipolygon.getOuterWays().isEmpty()) {
    496423            for (PolyData pd : multipolygon.getCombinedPolygons()) {
    497                 Path2D.Double p = pd.get();
     424                MapViewPath p = new MapViewPath(mapState);
     425                p.appendFromEastNorth(pd.get());
     426                p.setWindingRule(Path2D.WIND_EVEN_ODD);
    498427                Path2D.Double pfClip = null;
    499428                if (!isAreaVisible(p)) {
     
    574503        g.setFont(text.font);
    575504
    576         int x = (int) (Math.round(p.getInViewX()) + text.xOffset);
    577         int y = (int) (Math.round(p.getInViewY()) + text.yOffset);
     505        FontRenderContext frc = g.getFontRenderContext();
     506        Rectangle2D bounds = text.font.getStringBounds(s, frc);
     507
     508        double x = Math.round(p.getInViewX()) + text.xOffset + bounds.getCenterX();
     509        double y = Math.round(p.getInViewY()) + text.yOffset + bounds.getCenterY();
    578510        /**
    579511         *
     
    591523            x += box.x + box.width + 2;
    592524        } else {
    593             FontRenderContext frc = g.getFontRenderContext();
    594             Rectangle2D bounds = text.font.getStringBounds(s, frc);
    595525            int textWidth = (int) bounds.getWidth();
    596526            if (bs.hAlign == HorizontalTextAlignment.CENTER) {
     
    604534            y += box.y + box.height;
    605535        } else {
    606             FontRenderContext frc = g.getFontRenderContext();
    607536            LineMetrics metrics = text.font.getLineMetrics(s, frc);
    608537            if (bs.vAlign == VerticalTextAlignment.ABOVE) {
     
    616545            } else throw new AssertionError();
    617546        }
    618         displayText(null, s, x, y, n.isDisabled(), text);
     547
     548        displayText(n, text, s, bounds, new MapViewPositionAndRotation(mapState.getForView(x, y), 0));
    619549        g.setFont(defaultFont);
    620550    }
     
    757687
    758688        forEachPolygon(osm, path -> {
    759             Shape area = path.createTransformedShape(mapState.getAffineTransform());
    760             Rectangle2D placement = iconPosition.findLabelPlacement(area, iconRect);
     689            MapViewPositionAndRotation placement = iconPosition.findLabelPlacement(path, iconRect);
    761690            if (placement == null) {
    762691                return;
    763692            }
    764             MapViewPoint p = mapState.getForView(placement.getCenterX(), placement.getCenterY());
    765             drawIcon(p, img, disabled, selected, member, theta, (g, r) -> {
     693            MapViewPoint p = placement.getPoint();
     694            drawIcon(p, img, disabled, selected, member, theta + placement.getRotation(), (g, r) -> {
    766695                if (useStrokes) {
    767696                    g.setStroke(new BasicStroke(2));
     
    10711000
    10721001    /**
    1073      * A half segment that can be used to place text on it. Used in the drawTextOnPath algorithm.
    1074      * @author Michael Zangl
    1075      */
    1076     private static class HalfSegment {
    1077         /**
    1078          * start point of half segment (as length along the way)
    1079          */
    1080         final double start;
    1081         /**
    1082          * end point of half segment (as length along the way)
    1083          */
    1084         final double end;
    1085         /**
    1086          * quality factor (off screen / partly on screen / fully on screen)
    1087          */
    1088         final double quality;
    1089 
    1090         /**
    1091          * Create a new half segment
    1092          * @param start The start along the way
    1093          * @param end The end of the segment
    1094          * @param quality A quality factor.
    1095          */
    1096         HalfSegment(double start, double end, double quality) {
    1097             super();
    1098             this.start = start;
    1099             this.end = end;
    1100             this.quality = quality;
    1101         }
    1102 
    1103         @Override
    1104         public String toString() {
    1105             return "HalfSegment [start=" + start + ", end=" + end + ", quality=" + quality + "]";
    1106         }
    1107     }
    1108 
    1109     /**
    11101002     * Draws a text for the given primitive
    11111003     * @param osm The primitive to draw the text for
     
    11141006     */
    11151007    public void drawText(OsmPrimitive osm, TextLabel text) {
    1116         PositionForAreaStrategy position = text.getLabelPositionStrategy();
    1117         if (position.supportsGlyphVector()) {
    1118             if (osm instanceof Way) {
    1119                 // we might allow this for the outline of relations as well.
    1120                 drawTextOnPath((Way) osm, text);
    1121             }
     1008        if (!isShowNames()) {
     1009            return;
     1010        }
     1011        String name = text.getString(osm);
     1012        if (name == null || name.isEmpty()) {
     1013            return;
     1014        }
     1015
     1016        FontMetrics fontMetrics = g.getFontMetrics(text.font); // if slow, use cache
     1017        Rectangle2D nb = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font)
     1018
     1019        Font defaultFont = g.getFont();
     1020        forEachPolygon(osm, path -> {
     1021            //TODO: Ignore areas that are out of bounds.
     1022            PositionForAreaStrategy position = text.getLabelPositionStrategy();
     1023            MapViewPositionAndRotation center = text.getLabelPositionStrategy().findLabelPlacement(path, nb);
     1024            if (center != null) {
     1025                displayText(osm, text, name, nb, center);
     1026            } else if (position.supportsGlyphVector()) {
     1027                List<GlyphVector> gvs = Utils.getGlyphVectorsBidi(name, text.font, g.getFontRenderContext());
     1028
     1029                List<GlyphVector> translatedGvs = position.generateGlyphVectors(path, nb, gvs, isGlyphVectorDoubleTranslationBug(text.font));
     1030                displayText(() -> translatedGvs.forEach(gv -> g.drawGlyphVector(gv, 0, 0)),
     1031                        () -> translatedGvs.stream().collect(
     1032                                        () -> new Path2D.Double(),
     1033                                        (p, gv) -> p.append(gv.getOutline(0, 0), false),
     1034                                        (p1, p2) -> p1.append(p2, false)),
     1035                        osm.isDisabled(), text);
     1036            } else if (Main.isTraceEnabled()) {
     1037                Main.trace("Couldn't find a correct label placement for " + osm + " / " + name);
     1038            }
     1039        });
     1040        g.setFont(defaultFont);
     1041    }
     1042
     1043    private void displayText(OsmPrimitive osm, TextLabel text, String name, Rectangle2D nb,
     1044            MapViewPositionAndRotation center) {
     1045        AffineTransform at = AffineTransform.getTranslateInstance(center.getPoint().getInViewX(), center.getPoint().getInViewY());
     1046        at.rotate(center.getRotation());
     1047        at.translate(-nb.getCenterX(), -nb.getCenterY());
     1048        displayText(() -> {
     1049            AffineTransform defaultTransform = g.getTransform();
     1050            g.setTransform(at);
     1051            g.setFont(text.font);
     1052            g.drawString(name, 0, 0);
     1053            g.setTransform(defaultTransform);
     1054        }, () -> {
     1055            FontRenderContext frc = g.getFontRenderContext();
     1056            TextLayout tl = new TextLayout(name, text.font, frc);
     1057            return tl.getOutline(at);
     1058        }, osm.isDisabled(), text);
     1059    }
     1060
     1061    /**
     1062     * Displays text at specified position including its halo, if applicable.
     1063     *
     1064     * @param fill The function that fills the text
     1065     * @param outline The function to draw the outline
     1066     * @param disabled {@code true} if element is disabled (filtered out)
     1067     * @param text text style to use
     1068     */
     1069    private void displayText(Runnable fill, Supplier<Shape> outline, boolean disabled, TextLabel text) {
     1070        if (isInactiveMode || disabled) {
     1071            g.setColor(inactiveColor);
     1072            fill.run();
     1073        } else if (text.haloRadius != null) {
     1074            g.setStroke(new BasicStroke(2*text.haloRadius, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
     1075            g.setColor(text.haloColor);
     1076            Shape textOutline = outline.get();
     1077            g.draw(textOutline);
     1078            g.setStroke(new BasicStroke());
     1079            g.setColor(text.color);
     1080            g.fill(textOutline);
    11221081        } else {
    1123             forEachPolygon(osm, path -> drawAreaText(osm, text, path));
     1082            g.setColor(text.color);
     1083            fill.run();
    11241084        }
    11251085    }
     
    11301090     * @param consumer The consumer to call.
    11311091     */
    1132     private void forEachPolygon(OsmPrimitive osm, Consumer<Path2D.Double> consumer) {
     1092    private void forEachPolygon(OsmPrimitive osm, Consumer<MapViewPath> consumer) {
    11331093        if (osm instanceof Way) {
    11341094            consumer.accept(getPath((Way) osm));
     
    11371097            if (!multipolygon.getOuterWays().isEmpty()) {
    11381098                for (PolyData pd : multipolygon.getCombinedPolygons()) {
    1139                     consumer.accept(pd.get());
     1099                    MapViewPath path = new MapViewPath(mapState);
     1100                    path.appendFromEastNorth(pd.get());
     1101                    consumer.accept(path);
    11401102                }
    11411103            }
     
    11471109     * @param way The way to draw the text on.
    11481110     * @param text The text definition (font/.../text content) to draw.
    1149      */
     1111     * @deprecated Use {@link #drawText(OsmPrimitive, TextLabel)} instead.
     1112     */
     1113    @Deprecated
    11501114    public void drawTextOnPath(Way way, TextLabel text) {
    1151         if (way == null || text == null)
    1152             return;
    1153         String name = text.getString(way);
    1154         if (name == null || name.isEmpty())
    1155             return;
    1156 
    1157         FontMetrics fontMetrics = g.getFontMetrics(text.font);
    1158         Rectangle2D rec = fontMetrics.getStringBounds(name, g);
    1159 
    1160         Rectangle bounds = g.getClipBounds();
    1161 
    1162         List<MapViewPoint> points = way.getNodes().stream().map(mapState::getPointFor).collect(Collectors.toList());
    1163 
    1164         // find half segments that are long enough to draw text on (don't draw text over the cross hair in the center of each segment)
    1165         List<HalfSegment> longHalfSegment = new ArrayList<>();
    1166 
    1167         double pathLength = computePath(2 * (rec.getWidth() + 4), bounds, points, longHalfSegment);
    1168 
    1169         if (rec.getWidth() > pathLength)
    1170             return;
    1171 
    1172         double t1, t2;
    1173 
    1174         if (!longHalfSegment.isEmpty()) {
    1175             // find the segment with the best quality. If there are several with best quality, the one close to the center is prefered.
    1176             Optional<HalfSegment> besto = longHalfSegment.stream().max(
    1177                     Comparator.comparingDouble(segment ->
    1178                         segment.quality - 1e-5 * Math.abs(0.5 * (segment.end + segment.start) - 0.5 * pathLength)
    1179                     ));
    1180             if (!besto.isPresent())
    1181                 throw new IllegalStateException("Unable to find the segment with the best quality for " + way);
    1182             HalfSegment best = besto.get();
    1183             double remaining = best.end - best.start - rec.getWidth(); // total space left and right from the text
    1184             // The space left and right of the text should be distributed 20% - 80% (towards the center),
    1185             // but the smaller space should not be less than 7 px.
    1186             // However, if the total remaining space is less than 14 px, then distribute it evenly.
    1187             double smallerSpace = Math.min(Math.max(0.2 * remaining, 7), 0.5 * remaining);
    1188             if ((best.end + best.start)/2 < pathLength/2) {
    1189                 t2 = best.end - smallerSpace;
    1190                 t1 = t2 - rec.getWidth();
    1191             } else {
    1192                 t1 = best.start + smallerSpace;
    1193                 t2 = t1 + rec.getWidth();
    1194             }
    1195         } else {
    1196             // doesn't fit into one half-segment -> just put it in the center of the way
    1197             t1 = pathLength/2 - rec.getWidth()/2;
    1198             t2 = pathLength/2 + rec.getWidth()/2;
    1199         }
    1200         t1 /= pathLength;
    1201         t2 /= pathLength;
    1202 
    1203         double[] p1 = pointAt(t1, points, pathLength);
    1204         double[] p2 = pointAt(t2, points, pathLength);
    1205 
    1206         if (p1 == null || p2 == null)
    1207             return;
    1208 
    1209         double angleOffset;
    1210         double offsetSign;
    1211         double tStart;
    1212 
    1213         if (p1[0] < p2[0] &&
    1214                 p1[2] < Math.PI/2 &&
    1215                 p1[2] > -Math.PI/2) {
    1216             angleOffset = 0;
    1217             offsetSign = 1;
    1218             tStart = t1;
    1219         } else {
    1220             angleOffset = Math.PI;
    1221             offsetSign = -1;
    1222             tStart = t2;
    1223         }
    1224 
    1225         List<GlyphVector> gvs = Utils.getGlyphVectorsBidi(name, text.font, g.getFontRenderContext());
    1226         double gvOffset = 0;
    1227         for (GlyphVector gv : gvs) {
    1228             double gvWidth = gv.getLogicalBounds().getBounds2D().getWidth();
    1229             for (int i = 0; i < gv.getNumGlyphs(); ++i) {
    1230                 Rectangle2D rect = gv.getGlyphLogicalBounds(i).getBounds2D();
    1231                 double t = tStart + offsetSign * (gvOffset + rect.getX() + rect.getWidth()/2) / pathLength;
    1232                 double[] p = pointAt(t, points, pathLength);
    1233                 if (p != null) {
    1234                     AffineTransform trfm = AffineTransform.getTranslateInstance(p[0] - rect.getX(), p[1]);
    1235                     trfm.rotate(p[2]+angleOffset);
    1236                     double off = -rect.getY() - rect.getHeight()/2 + text.yOffset;
    1237                     trfm.translate(-rect.getWidth()/2, off);
    1238                     if (isGlyphVectorDoubleTranslationBug(text.font)) {
    1239                         // scale the translation components by one half
    1240                         AffineTransform tmp = AffineTransform.getTranslateInstance(-0.5 * trfm.getTranslateX(), -0.5 * trfm.getTranslateY());
    1241                         tmp.concatenate(trfm);
    1242                         trfm = tmp;
    1243                     }
    1244                     gv.setGlyphTransform(i, trfm);
    1245                 }
    1246             }
    1247             displayText(gv, null, 0, 0, way.isDisabled(), text);
    1248             gvOffset += gvWidth;
    1249         }
    1250     }
    1251 
    1252     private static double computePath(double minSegmentLength, Rectangle bounds, List<MapViewPoint> points,
    1253             List<HalfSegment> longHalfSegment) {
    1254         MapViewPoint lastPoint = points.get(0);
    1255         double pathLength = 0;
    1256         for (MapViewPoint p : points.subList(1, points.size())) {
    1257             double segmentLength = p.distanceToInView(lastPoint);
    1258             if (segmentLength > minSegmentLength) {
    1259                 Point2D center = new Point2D.Double((lastPoint.getInViewX() + p.getInViewX())/2, (lastPoint.getInViewY() + p.getInViewY())/2);
    1260                 double q = computeQuality(bounds, lastPoint, center);
    1261                 // prefer the first one for quality equality.
    1262                 longHalfSegment.add(new HalfSegment(pathLength, pathLength + segmentLength / 2, q));
    1263 
    1264                 q = 0;
    1265                 if (bounds != null) {
    1266                     if (bounds.contains(center) && bounds.contains(p.getInView())) {
    1267                         q = 2;
    1268                     } else if (bounds.contains(center) || bounds.contains(p.getInView())) {
    1269                         q = 1;
    1270                     }
    1271                 }
    1272                 longHalfSegment.add(new HalfSegment(pathLength + segmentLength / 2, pathLength + segmentLength, q));
    1273             }
    1274             pathLength += segmentLength;
    1275             lastPoint = p;
    1276         }
    1277         return pathLength;
    1278     }
    1279 
    1280     private static double computeQuality(Rectangle bounds, MapViewPoint p1, Point2D p2) {
    1281         double q = 0;
    1282         if (bounds != null) {
    1283             if (bounds.contains(p1.getInView())) {
    1284                 q += 1;
    1285             }
    1286             if (bounds.contains(p2)) {
    1287                 q += 1;
    1288             }
    1289         }
    1290         return q;
     1115        // NOP.
    12911116    }
    12921117
     
    14841309    }
    14851310
    1486     private static Path2D.Double getPath(Way w) {
    1487         Path2D.Double path = new Path2D.Double();
    1488         boolean initial = true;
    1489         for (Node n : w.getNodes()) {
    1490             EastNorth p = n.getEastNorth();
    1491             if (p != null) {
    1492                 if (initial) {
    1493                     path.moveTo(p.getX(), p.getY());
    1494                     initial = false;
    1495                 } else {
    1496                     path.lineTo(p.getX(), p.getY());
    1497                 }
    1498             }
    1499         }
     1311    private MapViewPath getPath(Way w) {
     1312        MapViewPath path = new MapViewPath(mapState);
    15001313        if (w.isClosed()) {
    1501             path.closePath();
     1314            path.appendClosed(w.getNodes(), false);
     1315        } else {
     1316            path.append(w.getNodes(), false);
    15021317        }
    15031318        return path;
     
    16311446    public boolean isShowNames() {
    16321447        return showNames;
    1633     }
    1634 
    1635     private static double[] pointAt(double t, List<MapViewPoint> poly, double pathLength) {
    1636         double totalLen = t * pathLength;
    1637         double curLen = 0;
    1638         double dx, dy;
    1639         double segLen;
    1640 
    1641         // Yes, it is inefficient to iterate from the beginning for each glyph.
    1642         // Can be optimized if it turns out to be slow.
    1643         for (int i = 1; i < poly.size(); ++i) {
    1644             dx = poly.get(i).getInViewX() - poly.get(i - 1).getInViewX();
    1645             dy = poly.get(i).getInViewY() - poly.get(i - 1).getInViewY();
    1646             segLen = Math.sqrt(dx*dx + dy*dy);
    1647             if (totalLen > curLen + segLen) {
    1648                 curLen += segLen;
    1649                 continue;
    1650             }
    1651             return new double[] {
    1652                     poly.get(i - 1).getInViewX() + (totalLen - curLen) / segLen * dx,
    1653                     poly.get(i - 1).getInViewY() + (totalLen - curLen) / segLen * dy,
    1654                     Math.atan2(dy, dx)};
    1655         }
    1656         return null;
    16571448    }
    16581449
  • trunk/src/org/openstreetmap/josm/gui/draw/MapViewPath.java

    r11225 r11748  
    55import java.awt.Shape;
    66import java.awt.Stroke;
     7import java.awt.geom.Path2D;
    78import java.awt.geom.PathIterator;
    89
     
    4445
    4546    /**
     47     * Gets the map view state this path is used for.
     48     * @return The state.
     49     * @since 11748
     50     */
     51    public MapViewState getMapViewState() {
     52        return state;
     53    }
     54
     55    /**
    4656     * Move the cursor to the given node.
    4757     * @param n The node
     
    171181
    172182    /**
     183     * Converts a path in east/north coordinates to view space.
     184     * @param path The path
     185     * @since 11748
     186     */
     187    public void appendFromEastNorth(Path2D.Double path) {
     188        new AbstractPathVisitor() {
     189            @Override
     190            void visitMoveTo(double x, double y) {
     191                moveTo(new EastNorth(x, y));
     192            }
     193
     194            @Override
     195            void visitLineTo(double x, double y) {
     196                lineTo(new EastNorth(x, y));
     197            }
     198
     199            @Override
     200            void visitClose() {
     201                closePath();
     202            }
     203        }.visit(path);
     204    }
     205
     206    /**
     207     * Visits all segments of this path.
     208     * @param consumer The consumer to send path segments to
     209     * @return the total line length
     210     * @since 11748
     211     */
     212    public double visitLine(PathSegmentConsumer consumer) {
     213        LineVisitor visitor = new LineVisitor(consumer);
     214        visitor.visit(this);
     215        return visitor.inLineOffset;
     216    }
     217
     218    /**
    173219     * Compute a line that is similar to the current path expect for that parts outside the screen are skipped using moveTo commands.
    174220     *
     
    229275    }
    230276
     277    /**
     278     * Gets the length of the way in visual space.
     279     * @return The length.
     280     * @since 11748
     281     */
     282    public double getLength() {
     283        return visitLine((inLineOffset, start, end, startIsOldEnd) -> { });
     284    }
    231285
    232286    /**
     
    246300         */
    247301        void addLineBetween(double inLineOffset, MapViewPoint start, MapViewPoint end, boolean startIsOldEnd);
    248 
    249     }
    250 
    251     private class ClampingPathVisitor {
    252         private final MapViewRectangle clip;
    253         private final PathSegmentConsumer consumer;
    254         protected double strokeProgress;
    255         private final double strokeLength;
    256         private MapViewPoint lastMoveTo;
    257 
    258         private MapViewPoint cursor;
    259         private boolean cursorIsActive;
    260 
    261         /**
    262          * Create a new {@link ClampingPathVisitor}
    263          * @param clip View clip rectangle
    264          * @param strokeOffset Initial stroke offset
    265          * @param strokeLength Total length of a stroke sequence
    266          * @param consumer The consumer to notify of the path segments.
    267          */
    268         ClampingPathVisitor(MapViewRectangle clip, double strokeOffset, double strokeLength, PathSegmentConsumer consumer) {
    269             this.clip = clip;
    270             this.strokeProgress = Math.min(strokeLength - strokeOffset, 0);
    271             this.strokeLength = strokeLength;
    272             this.consumer = consumer;
    273         }
    274 
     302    }
     303
     304    private abstract static class AbstractPathVisitor {
    275305        /**
    276306         * Append a path to this one. The path is clipped to the current view.
    277          * @param mapViewPath The iterator
     307         * @param path The iterator
    278308         * @return true if adding the path was successful.
    279309         */
    280         public boolean visit(MapViewPath mapViewPath) {
     310        public boolean visit(Path2D.Double path) {
    281311            double[] coords = new double[8];
    282             PathIterator it = mapViewPath.getPathIterator(null);
     312            PathIterator it = path.getPathIterator(null);
    283313            while (!it.isDone()) {
    284314                int type = it.currentSegment(coords);
     
    294324                    break;
    295325                default:
    296                     // cannot handle this shape - this should be very rare. We let Java2D do the clipping.
     326                    // cannot handle this shape - this should be very rare and not happening in OSM draw code.
    297327                    return false;
    298328                }
     
    302332        }
    303333
     334        abstract void visitClose();
     335
     336        abstract void visitMoveTo(double x, double y);
     337
     338        abstract void visitLineTo(double x, double y);
     339    }
     340
     341    private abstract class AbstractMapPathVisitor extends AbstractPathVisitor {
     342        private MapViewPoint lastMoveTo;
     343
     344        @Override
     345        void visitMoveTo(double x, double y) {
     346            MapViewPoint move = state.getForView(x, y);
     347            lastMoveTo = move;
     348            visitMoveTo(move);
     349        }
     350
     351        abstract void visitMoveTo(MapViewPoint p);
     352
     353        @Override
     354        void visitLineTo(double x, double y) {
     355            visitLineTo(state.getForView(x, y));
     356        }
     357
     358        abstract void visitLineTo(MapViewPoint p);
     359
     360        @Override
    304361        void visitClose() {
    305             drawLineTo(lastMoveTo);
    306         }
    307 
    308         void visitMoveTo(double x, double y) {
    309             MapViewPoint point = state.getForView(x, y);
    310             lastMoveTo = point;
     362            visitLineTo(lastMoveTo);
     363        }
     364    }
     365
     366    private final class LineVisitor extends AbstractMapPathVisitor {
     367        private final PathSegmentConsumer consumer;
     368        private MapViewPoint last;
     369        private double inLineOffset = 0;
     370        private boolean startIsOldEnd = false;
     371
     372        LineVisitor(PathSegmentConsumer consumer) {
     373            this.consumer = consumer;
     374        }
     375
     376        @Override
     377        void visitMoveTo(MapViewPoint p) {
     378            last = p;
     379            startIsOldEnd = false;
     380        }
     381
     382        @Override
     383        void visitLineTo(MapViewPoint p) {
     384            consumer.addLineBetween(inLineOffset, last, p, startIsOldEnd);
     385            inLineOffset += last.distanceToInView(p);
     386            last = p;
     387            startIsOldEnd = true;
     388        }
     389    }
     390
     391    private class ClampingPathVisitor extends AbstractMapPathVisitor {
     392        private final MapViewRectangle clip;
     393        private final PathSegmentConsumer consumer;
     394        protected double strokeProgress;
     395        private final double strokeLength;
     396
     397        private MapViewPoint cursor;
     398        private boolean cursorIsActive;
     399
     400        /**
     401         * Create a new {@link ClampingPathVisitor}
     402         * @param clip View clip rectangle
     403         * @param strokeOffset Initial stroke offset
     404         * @param strokeLength Total length of a stroke sequence
     405         * @param consumer The consumer to notify of the path segments.
     406         */
     407        ClampingPathVisitor(MapViewRectangle clip, double strokeOffset, double strokeLength, PathSegmentConsumer consumer) {
     408            this.clip = clip;
     409            this.strokeProgress = Math.min(strokeLength - strokeOffset, 0);
     410            this.strokeLength = strokeLength;
     411            this.consumer = consumer;
     412        }
     413
     414        @Override
     415        void visitMoveTo(MapViewPoint point) {
    311416            cursor = point;
    312417            cursorIsActive = false;
    313418        }
    314419
    315         void visitLineTo(double x, double y) {
    316             drawLineTo(state.getForView(x, y));
    317         }
    318 
    319         private void drawLineTo(MapViewPoint next) {
     420        @Override
     421        void visitLineTo(MapViewPoint next) {
    320422            MapViewPoint entry = clip.getLineEntry(cursor, next);
    321423            if (entry != null) {
     
    348450        }
    349451    }
     452
    350453}
  • trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/AreaIconElement.java

    r11731 r11748  
    99import org.openstreetmap.josm.gui.mappaint.Cascade;
    1010import org.openstreetmap.josm.gui.mappaint.Environment;
     11import org.openstreetmap.josm.gui.mappaint.styleelement.placement.PartiallyInsideAreaStrategy;
     12import org.openstreetmap.josm.gui.mappaint.styleelement.placement.PositionForAreaStrategy;
    1113import org.openstreetmap.josm.gui.util.RotationAngle;
    1214
     
    3032     * The position of the icon inside the area.
    3133     */
    32     private final PositionForAreaStrategy iconPosition = PositionForAreaStrategy.PARTIALY_INSIDE;
     34    private final PositionForAreaStrategy iconPosition = PartiallyInsideAreaStrategy.INSTANCE;
    3335
    3436    protected AreaIconElement(Cascade c, MapImage iconImage, RotationAngle iconImageAngle) {
  • trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/LineTextElement.java

    r11722 r11748  
    55
    66import org.openstreetmap.josm.data.osm.OsmPrimitive;
    7 import org.openstreetmap.josm.data.osm.Way;
    87import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
    98import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
     
    4342    public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter,
    4443            boolean selected, boolean outermember, boolean member) {
    45         Way w = (Way) primitive;
    46         painter.drawTextOnPath(w, text);
     44        // nop
    4745    }
    4846
  • trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/TextElement.java

    r11723 r11748  
    1111import org.openstreetmap.josm.gui.mappaint.Environment;
    1212import org.openstreetmap.josm.gui.mappaint.Keyword;
     13import org.openstreetmap.josm.gui.mappaint.styleelement.placement.CompletelyInsideAreaStrategy;
    1314
    1415/**
     
    5859            return null;
    5960        }
    60         return new TextElement(c, text.withPosition(PositionForAreaStrategy.INSIDE));
     61        return new TextElement(c, text.withPosition(CompletelyInsideAreaStrategy.INSTANCE));
    6162    }
    6263
  • trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/TextLabel.java

    r11731 r11748  
    1515import org.openstreetmap.josm.gui.mappaint.styleelement.LabelCompositionStrategy.StaticLabelCompositionStrategy;
    1616import org.openstreetmap.josm.gui.mappaint.styleelement.LabelCompositionStrategy.TagLookupCompositionStrategy;
    17 import org.openstreetmap.josm.gui.mappaint.styleelement.PositionForAreaStrategy.CompletelyInsideAreaStrategy;
     17import org.openstreetmap.josm.gui.mappaint.styleelement.placement.CompletelyInsideAreaStrategy;
     18import org.openstreetmap.josm.gui.mappaint.styleelement.placement.PositionForAreaStrategy;
    1819import org.openstreetmap.josm.tools.CheckParameterUtil;
    1920import org.openstreetmap.josm.tools.Utils;
Note: See TracChangeset for help on using the changeset viewer.