Ticket #17119: faster-complex-mp.patch

File faster-complex-mp.patch, 15.3 KB (added by GerdP, 6 years ago)
  • src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java

     
    8787import org.openstreetmap.josm.tools.ImageProvider;
    8888import org.openstreetmap.josm.tools.JosmRuntimeException;
    8989import org.openstreetmap.josm.tools.Logging;
     90import org.openstreetmap.josm.tools.ShapeClipper;
    9091import org.openstreetmap.josm.tools.Utils;
    9192import org.openstreetmap.josm.tools.bugreport.BugReport;
    9293
     
    436437     * @param disabled If this should be drawn with a special disabled style.
    437438     */
    438439    protected void drawArea(MapViewPath path, Color color,
    439             MapImage fillImage, Float extent, Path2D.Double pfClip, boolean disabled) {
     440            MapImage fillImage, Float extent, MapViewPath pfClip, boolean disabled) {
    440441        if (!isOutlineOnly && color.getAlpha() != 0) {
    441442            Shape area = path;
    442443            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
     
    477478     * @param mitterLimit parameter for BasicStroke
    478479     *
    479480     */
    480     private void computeFill(Shape shape, Float extent, Path2D.Double pfClip, float mitterLimit) {
     481    private void computeFill(Shape shape, Float extent, MapViewPath pfClip, float mitterLimit) {
    481482        if (extent == null) {
    482483            g.fill(shape);
    483484        } else {
     
    484485            Shape oldClip = g.getClip();
    485486            Shape clip = shape;
    486487            if (pfClip != null) {
    487                 clip = pfClip.createTransformedShape(mapState.getAffineTransform());
     488                clip = pfClip;
    488489            }
    489490            g.clip(clip);
    490491            g.setStroke(new BasicStroke(2 * extent, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, mitterLimit));
     
    514515                if (!isAreaVisible(pd.get())) {
    515516                    continue;
    516517                }
    517                 MapViewPath p = new MapViewPath(mapState);
    518                 p.appendFromEastNorth(pd.get());
    519                 p.setWindingRule(Path2D.WIND_EVEN_ODD);
    520                 Path2D.Double pfClip = null;
     518                MapViewPath p = shapeEastNorthToMapView(pd.get());
     519                MapViewPath pfClip = null;
    521520                if (extent != null) {
    522521                    if (!usePartialFill(pd.getAreaAndPerimeter(null), extent, extentThreshold)) {
    523522                        extent = null;
    524523                    } else if (!pd.isClosed()) {
    525                         pfClip = getPFClip(pd, extent * scale);
     524                        pfClip = shapeEastNorthToMapView(getPFClip(pd, extent * scale));
    526525                    }
    527526                }
    528527                drawArea(p,
     
    533532    }
    534533
    535534    /**
     535     * Convert shape in EastNorth coordinates to MapViewPath and remove invisible parts.
     536     * For complex shapes this improves performance drastically because the methods in Graphics2D.clip() and Graphics2D.draw() are rather slow.
     537     * @param shape the shape to convert
     538     * @return the converted shape
     539     */
     540    private MapViewPath shapeEastNorthToMapView(Path2D.Double shape) {
     541        MapViewPath convertedShape = null;
     542        if (shape != null) {
     543            convertedShape = new MapViewPath(mapState);
     544            convertedShape.appendFromEastNorth(shape);
     545            convertedShape.setWindingRule(Path2D.WIND_EVEN_ODD);
     546
     547            Rectangle2D extViewBBox = mapState.getViewClipRectangle().getInView();
     548            if (!extViewBBox.contains(convertedShape.getBounds2D())) {
     549                // remove invisible parts of shape
     550                Path2D.Double clipped = ShapeClipper.clipShape(convertedShape, extViewBBox);
     551                if (clipped != null) {
     552                    convertedShape.reset();
     553                    convertedShape.append(clipped, false);
     554                }
     555            }
     556        }
     557        return convertedShape;
     558    }
     559
     560    /**
    536561     * Draws an area defined by a way. They way does not need to be closed, but it should.
    537562     * @param w The way.
    538563     * @param color The color to fill the area with.
     
    546571     * @since 12285
    547572     */
    548573    public void drawArea(IWay<?> w, Color color, MapImage fillImage, Float extent, Float extentThreshold, boolean disabled) {
    549         Path2D.Double pfClip = null;
     574        MapViewPath pfClip = null;
    550575        if (extent != null) {
    551576            if (!usePartialFill(Geometry.getAreaAndPerimeter(w.getNodes()), extent, extentThreshold)) {
    552577                extent = null;
    553578            } else if (!w.isClosed()) {
    554                 pfClip = getPFClip(w, extent * scale);
     579                pfClip = shapeEastNorthToMapView(getPFClip(w, extent * scale));
    555580            }
    556581        }
    557582        drawArea(getPath(w), color, fillImage, extent, pfClip, disabled);
  • src/org/openstreetmap/josm/tools/ShapeClipper.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools;
     3
     4import java.awt.Shape;
     5import java.awt.geom.Path2D;
     6import java.awt.geom.PathIterator;
     7import java.awt.geom.Rectangle2D;
     8import java.util.Arrays;
     9
     10/**
     11 * Tools to clip a shape based on the Sutherland-Hodgman algorithm.
     12 * See https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm
     13 * @author Gerd Petermann
     14 *
     15 */
     16public final class ShapeClipper {
     17    private static final int LEFT = 0;
     18    private static final int TOP = 1;
     19    private static final int RIGHT = 2;
     20    private static final int BOTTOM = 3;
     21
     22    private ShapeClipper() {
     23        // Hide default constructor for utils classes
     24    }
     25
     26    /**
     27     * Clip a given (closed) shape with a given rectangle.
     28     * @param shape the subject shape to clip
     29     * @param clippingRect the clipping rectangle
     30     * @return the intersection of the shape and the rectangle
     31     * or null if they don't intersect or the shape is not closed.
     32     * The intersection may contain dangling edges.
     33     */
     34    public static Path2D.Double clipShape(Shape shape, Rectangle2D clippingRect) {
     35        Path2D.Double result = null;
     36        double minX, minY, maxX, maxY;
     37        int num = 0;
     38        minX = minY = Double.POSITIVE_INFINITY;
     39        maxX = maxY = Double.NEGATIVE_INFINITY;
     40
     41        PathIterator pit = shape.getPathIterator(null);
     42        double[] points = new double[512];
     43        double[] res = new double[8];
     44        while (true) {
     45            int type = pit.currentSegment(res);
     46            if (num > 0 && (type == PathIterator.SEG_CLOSE || type == PathIterator.SEG_MOVETO || pit.isDone())) {
     47                // we have extracted a single segment, maybe unclosed
     48                Path2D.Double segment = null;
     49                if (clippingRect.contains(minX, minY) && clippingRect.contains(maxX, maxY)) {
     50                    // all points are inside clipping rectangle
     51                    segment = pointsToPath2D(points, num);
     52                } else {
     53                    Rectangle2D.Double bbox = new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
     54                    segment = clipSinglePathWithSutherlandHodgman(points, num, clippingRect, bbox);
     55                }
     56                if (segment != null) {
     57                    if (type == PathIterator.SEG_CLOSE)
     58                        segment.closePath();
     59                    if (result == null)
     60                        result = segment;
     61                    else
     62                        result.append(segment, false);
     63                }
     64                if (pit.isDone())
     65                    break;
     66                num = 0;
     67                minX = minY = Double.POSITIVE_INFINITY;
     68                maxX = maxY = Double.NEGATIVE_INFINITY;
     69            }
     70            double x = res[0];
     71            double y = res[1];
     72            if (x < minX)
     73                minX = x;
     74            if (x > maxX)
     75                maxX = x;
     76            if (y < minY)
     77                minY = y;
     78            if (y > maxY)
     79                maxY = y;
     80            if (type == PathIterator.SEG_LINETO || type == PathIterator.SEG_MOVETO) {
     81                if (num + 2 >= points.length) {
     82                    points = Arrays.copyOf(points, points.length * 2);
     83                }
     84                points[num++] = x;
     85                points[num++] = y;
     86            } else if (type != PathIterator.SEG_CLOSE) {
     87                    //Logging.warn("unhandled path iterator");
     88            }
     89            pit.next();
     90        }
     91
     92        return result;
     93    }
     94
     95    /**
     96     * Convert a list of points to a Path2D.Double
     97     * @param points the pairs
     98     * @param num the number of valid values in points
     99     * @return the path or null if the path describes a point or line.
     100     */
     101    private static Path2D.Double pointsToPath2D(double[] points, int num) {
     102        if (num < 2)
     103            return null;
     104        if (points[0] == points[num - 2] && points[1] == points[num - 1])
     105            num -= 2;
     106        if (num < 6)
     107            return null;
     108        Path2D.Double path = new Path2D.Double();
     109        double lastX = points[0], lastY = points[1];
     110        path.moveTo(lastX, lastY);
     111        int numOut = 1;
     112        for (int i = 2; i < num;) {
     113            double x = points[i++], y = points[i++];
     114            if (x != lastX || y != lastY) {
     115                path.lineTo(x, y);
     116                lastX = x;
     117                lastY = y;
     118                ++numOut;
     119            }
     120        }
     121        if (numOut < 3)
     122            return null;
     123        return path;
     124    }
     125
     126    /**
     127     * Clip a single path with a given rectangle using the Sutherland-Hodgman algorithm. This is much faster compared to
     128     * the area.intersect method, but may create dangling edges.
     129     * @param points a list of longitude+latitude pairs
     130     * @param num the number of valid values in points
     131     * @param clippingRect the clipping rectangle
     132     * @param bbox the bounding box of the path
     133     * @return the clipped path as a Path2D.Double or null if the result is empty
     134     */
     135    private static Path2D.Double clipSinglePathWithSutherlandHodgman(double[] points, int num, Rectangle2D clippingRect,
     136            Rectangle2D.Double bbox) {
     137        if (num <= 2 || !bbox.intersects(clippingRect)) {
     138            return null;
     139        }
     140
     141        int countVals = num;
     142        if (points[0] == points[num - 2] && points[1] == points[num - 1]) {
     143            countVals -= 2;
     144        }
     145        double[] outputList = points;
     146        double[] input;
     147
     148        double leftX = clippingRect.getMinX();
     149        double rightX = clippingRect.getMaxX();
     150        double lowerY = clippingRect.getMinY();
     151        double upperY = clippingRect.getMaxY();
     152        boolean eIsIn = false, sIsIn = false;
     153        for (int side = LEFT; side <= BOTTOM; side++) {
     154            if (countVals < 6)
     155                return null; // ignore point or line
     156
     157            boolean skipTestForThisSide;
     158            switch (side) {
     159            case LEFT:
     160                skipTestForThisSide = (bbox.getMinX() >= leftX);
     161                break;
     162            case TOP:
     163                skipTestForThisSide = (bbox.getMaxY() < upperY);
     164                break;
     165            case RIGHT:
     166                skipTestForThisSide = (bbox.getMaxX() < rightX);
     167                break;
     168            default:
     169                skipTestForThisSide = (bbox.getMinY() >= lowerY);
     170            }
     171            if (skipTestForThisSide)
     172                continue;
     173
     174            input = outputList;
     175            outputList = new double[countVals + 16];
     176            double sLon = 0, sLat = 0;
     177            double pLon = 0, pLat = 0; // intersection
     178            int posIn = countVals - 2;
     179            int posOut = 0;
     180            for (int i = 0; i < countVals + 2; i += 2) {
     181                if (posIn >= countVals)
     182                    posIn = 0;
     183                double eLon = input[posIn++];
     184                double eLat = input[posIn++];
     185                switch (side) {
     186                case LEFT:
     187                    eIsIn = (eLon >= leftX);
     188                    break;
     189                case TOP:
     190                    eIsIn = (eLat < upperY);
     191                    break;
     192                case RIGHT:
     193                    eIsIn = (eLon < rightX);
     194                    break;
     195                default:
     196                    eIsIn = (eLat >= lowerY);
     197                }
     198                if (i > 0) {
     199                    if (eIsIn != sIsIn) {
     200                        // compute intersection
     201                        double slope;
     202                        if (eLon != sLon)
     203                            slope = (eLat - sLat) / (eLon - sLon);
     204                        else
     205                            slope = 1;
     206
     207                        switch (side) {
     208                        case LEFT:
     209                            pLon = leftX;
     210                            pLat = slope * (leftX - sLon) + sLat;
     211                            break;
     212                        case RIGHT:
     213                            pLon = rightX;
     214                            pLat = slope * (rightX - sLon) + sLat;
     215                            break;
     216
     217                        case TOP:
     218                            if (eLon != sLon)
     219                                pLon = sLon + (upperY - sLat) / slope;
     220                            else
     221                                pLon = sLon;
     222                            pLat = upperY;
     223                            break;
     224                        default: // BOTTOM
     225                            if (eLon != sLon)
     226                                pLon = sLon + (lowerY - sLat) / slope;
     227                            else
     228                                pLon = sLon;
     229                            pLat = lowerY;
     230                            break;
     231
     232                        }
     233                    }
     234                    int toAdd = 0;
     235                    if (eIsIn) {
     236                        if (!sIsIn) {
     237                            toAdd += 2;
     238                        }
     239                        toAdd += 2;
     240                    } else {
     241                        if (sIsIn) {
     242                            toAdd += 2;
     243                        }
     244                    }
     245                    if (posOut + toAdd >= outputList.length) {
     246                        // unlikely
     247                        outputList = Arrays.copyOf(outputList, outputList.length * 2);
     248                    }
     249                    if (eIsIn) {
     250                        if (!sIsIn) {
     251                            outputList[posOut++] = pLon;
     252                            outputList[posOut++] = pLat;
     253                        }
     254                        outputList[posOut++] = eLon;
     255                        outputList[posOut++] = eLat;
     256                    } else {
     257                        if (sIsIn) {
     258                            outputList[posOut++] = pLon;
     259                            outputList[posOut++] = pLat;
     260                        }
     261                    }
     262                }
     263                // S = E
     264                sLon = eLon;
     265                sLat = eLat;
     266                sIsIn = eIsIn;
     267            }
     268            countVals = posOut;
     269        }
     270        return pointsToPath2D(outputList, countVals);
     271    }
     272}