Ticket #17119: clip.patch

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