Ticket #17119: faster-complex-mp-v3.patch

File faster-complex-mp-v3.patch, 15.3 KB (added by GerdP, 5 years ago)

reworked loop control, something is special with the pathIterator

  • 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 util 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 = new Path2D.Double();
     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[6];
     44        while (!pit.isDone()) {
     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                addToResult(result, points, num, minX, minY, maxX, maxY, clippingRect);
     49                num = 0;
     50                minX = minY = Double.POSITIVE_INFINITY;
     51                maxX = maxY = Double.NEGATIVE_INFINITY;
     52            }
     53            double x = res[0];
     54            double y = res[1];
     55            if (x < minX)
     56                minX = x;
     57            if (x > maxX)
     58                maxX = x;
     59            if (y < minY)
     60                minY = y;
     61            if (y > maxY)
     62                maxY = y;
     63            if (type == PathIterator.SEG_LINETO || type == PathIterator.SEG_MOVETO) {
     64                if (num + 2 >= points.length) {
     65                    points = Arrays.copyOf(points, points.length * 2);
     66                }
     67                points[num++] = x;
     68                points[num++] = y;
     69            } else if (type != PathIterator.SEG_CLOSE) {
     70                    //Logging.warn("unhandled path iterator");
     71            }
     72            pit.next();
     73        }
     74        if (num > 2) {
     75            // last segment was not closed
     76            addToResult(result, points, num, minX, minY, maxX, maxY, clippingRect);
     77        }
     78        return result;
     79    }
     80
     81
     82    private static void addToResult(Path2D.Double result, double[] points, int num,
     83            double minX, double minY, double maxX, double maxY, Rectangle2D clippingRect) {
     84        Path2D.Double segment = null;
     85        if (clippingRect.contains(minX, minY) && clippingRect.contains(maxX, maxY)) {
     86            // all points are inside clipping rectangle
     87            segment = pointsToPath2D(points, num);
     88        } else {
     89            Rectangle2D.Double bbox = new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
     90            segment = clipSinglePathWithSutherlandHodgman(points, num, clippingRect, bbox);
     91        }
     92        if (segment != null) {
     93            result.append(segment, false);
     94        }
     95    }
     96
     97    /**
     98     * Convert a list of points to a Path2D.Double
     99     * @param points the pairs
     100     * @param num the number of valid values in points
     101     * @return the path or null if the path describes a point or line.
     102     */
     103    private static Path2D.Double pointsToPath2D(double[] points, int num) {
     104        if (num < 2)
     105            return null;
     106        if (points[0] == points[num - 2] && points[1] == points[num - 1])
     107            num -= 2;
     108        if (num < 6)
     109            return null;
     110        Path2D.Double path = new Path2D.Double();
     111        double lastX = points[0], lastY = points[1];
     112        path.moveTo(lastX, lastY);
     113        int numOut = 1;
     114        for (int i = 2; i < num;) {
     115            double x = points[i++], y = points[i++];
     116            if (x != lastX || y != lastY) {
     117                path.lineTo(x, y);
     118                lastX = x;
     119                lastY = y;
     120                ++numOut;
     121            }
     122        }
     123        if (numOut < 3)
     124            return null;
     125        return path;
     126    }
     127
     128    /**
     129     * Clip a single path with a given rectangle using the Sutherland-Hodgman algorithm. This is much faster compared to
     130     * the area.intersect method, but may create dangling edges.
     131     * @param points a list of longitude+latitude pairs
     132     * @param num the number of valid values in points
     133     * @param clippingRect the clipping rectangle
     134     * @param bbox the bounding box of the path
     135     * @return the clipped path as a Path2D.Double or null if the result is empty
     136     */
     137    private static Path2D.Double clipSinglePathWithSutherlandHodgman(double[] points, int num, Rectangle2D clippingRect,
     138            Rectangle2D.Double bbox) {
     139        if (num <= 2 || !bbox.intersects(clippingRect)) {
     140            return null;
     141        }
     142
     143        int countVals = num;
     144        if (points[0] == points[num - 2] && points[1] == points[num - 1]) {
     145            countVals -= 2;
     146        }
     147        double[] outputList = points;
     148        double[] input;
     149
     150        double leftX = clippingRect.getMinX();
     151        double rightX = clippingRect.getMaxX();
     152        double lowerY = clippingRect.getMinY();
     153        double upperY = clippingRect.getMaxY();
     154        boolean eIsIn = false, sIsIn = false;
     155        for (int side = LEFT; side <= BOTTOM; side++) {
     156            if (countVals < 6)
     157                return null; // ignore point or line
     158
     159            boolean skipTestForThisSide;
     160            switch (side) {
     161            case LEFT:
     162                skipTestForThisSide = (bbox.getMinX() >= leftX);
     163                break;
     164            case TOP:
     165                skipTestForThisSide = (bbox.getMaxY() < upperY);
     166                break;
     167            case RIGHT:
     168                skipTestForThisSide = (bbox.getMaxX() < rightX);
     169                break;
     170            default:
     171                skipTestForThisSide = (bbox.getMinY() >= lowerY);
     172            }
     173            if (skipTestForThisSide)
     174                continue;
     175
     176            input = outputList;
     177            outputList = new double[countVals + 16];
     178            double sLon = 0, sLat = 0;
     179            double pLon = 0, pLat = 0; // intersection
     180            int posIn = countVals - 2;
     181            int posOut = 0;
     182            for (int i = 0; i < countVals + 2; i += 2) {
     183                if (posIn >= countVals)
     184                    posIn = 0;
     185                double eLon = input[posIn++];
     186                double eLat = input[posIn++];
     187                switch (side) {
     188                case LEFT:
     189                    eIsIn = (eLon >= leftX);
     190                    break;
     191                case TOP:
     192                    eIsIn = (eLat < upperY);
     193                    break;
     194                case RIGHT:
     195                    eIsIn = (eLon < rightX);
     196                    break;
     197                default:
     198                    eIsIn = (eLat >= lowerY);
     199                }
     200                if (i > 0) {
     201                    if (eIsIn != sIsIn) {
     202                        // compute intersection
     203                        double slope;
     204                        if (eLon != sLon)
     205                            slope = (eLat - sLat) / (eLon - sLon);
     206                        else
     207                            slope = 1;
     208
     209                        switch (side) {
     210                        case LEFT:
     211                            pLon = leftX;
     212                            pLat = slope * (leftX - sLon) + sLat;
     213                            break;
     214                        case RIGHT:
     215                            pLon = rightX;
     216                            pLat = slope * (rightX - sLon) + sLat;
     217                            break;
     218
     219                        case TOP:
     220                            if (eLon != sLon)
     221                                pLon = sLon + (upperY - sLat) / slope;
     222                            else
     223                                pLon = sLon;
     224                            pLat = upperY;
     225                            break;
     226                        default: // BOTTOM
     227                            if (eLon != sLon)
     228                                pLon = sLon + (lowerY - sLat) / slope;
     229                            else
     230                                pLon = sLon;
     231                            pLat = lowerY;
     232                            break;
     233
     234                        }
     235                    }
     236                    int toAdd = 0;
     237                    if (eIsIn) {
     238                        if (!sIsIn) {
     239                            toAdd += 2;
     240                        }
     241                        toAdd += 2;
     242                    } else {
     243                        if (sIsIn) {
     244                            toAdd += 2;
     245                        }
     246                    }
     247                    if (posOut + toAdd >= outputList.length) {
     248                        // unlikely
     249                        outputList = Arrays.copyOf(outputList, outputList.length * 2);
     250                    }
     251                    if (eIsIn) {
     252                        if (!sIsIn) {
     253                            outputList[posOut++] = pLon;
     254                            outputList[posOut++] = pLat;
     255                        }
     256                        outputList[posOut++] = eLon;
     257                        outputList[posOut++] = eLat;
     258                    } else {
     259                        if (sIsIn) {
     260                            outputList[posOut++] = pLon;
     261                            outputList[posOut++] = pLat;
     262                        }
     263                    }
     264                }
     265                // S = E
     266                sLon = eLon;
     267                sLat = eLat;
     268                sIsIn = eIsIn;
     269            }
     270            countVals = posOut;
     271        }
     272        return pointsToPath2D(outputList, countVals);
     273    }
     274}