Ticket #17011: 17011-v1.patch

File 17011-v1.patch, 14.5 KB (added by GerdP, 5 years ago)
  • src/org/openstreetmap/josm/data/validation/OsmValidator.java

     
    5555import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
    5656import org.openstreetmap.josm.data.validation.tests.NameMismatch;
    5757import org.openstreetmap.josm.data.validation.tests.OpeningHourTest;
     58import org.openstreetmap.josm.data.validation.tests.OverlappingAreas;
    5859import org.openstreetmap.josm.data.validation.tests.OverlappingWays;
    5960import org.openstreetmap.josm.data.validation.tests.PowerLines;
    6061import org.openstreetmap.josm.data.validation.tests.PublicTransportRouteTest;
     
    148149        LongSegment.class, // 3500 .. 3599
    149150        PublicTransportRouteTest.class, // 3600 .. 3699
    150151        RightAngleBuildingTest.class, // 3700 .. 3799
     152        OverlappingAreas.class, // 3800 .. 3899
    151153    };
    152154
    153155    /**
  • src/org/openstreetmap/josm/data/validation/tests/OverlappingAreas.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.validation.tests;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.geom.Area;
     7import java.awt.geom.Path2D;
     8import java.awt.geom.PathIterator;
     9import java.util.ArrayList;
     10import java.util.List;
     11
     12import org.openstreetmap.josm.data.coor.EastNorth;
     13import org.openstreetmap.josm.data.osm.Node;
     14import org.openstreetmap.josm.data.osm.OsmPrimitive;
     15import org.openstreetmap.josm.data.osm.Relation;
     16import org.openstreetmap.josm.data.osm.Way;
     17import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
     18import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
     19import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
     20import org.openstreetmap.josm.data.validation.Severity;
     21import org.openstreetmap.josm.data.validation.Test;
     22import org.openstreetmap.josm.data.validation.TestError;
     23import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     24import org.openstreetmap.josm.tools.Geometry;
     25import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
     26import org.openstreetmap.josm.tools.Pair;
     27
     28/**
     29 * Tests if there are overlapping areas
     30 *
     31 * @author Gerd Petermann
     32 * @since xxx
     33 */
     34public class OverlappingAreas extends Test {
     35    protected static final int OVERLAPPING_AREA = 3800; // allows insideness
     36    protected static final int OVERLAPPING_WATER = 3801;
     37    protected static final int OVERLAPPING_IDENTICAL_NATURAL = 3802;
     38    protected static final int OVERLAPPING_IDENTICAL_LANDLUSE = 3803;
     39    protected static final int OVERLAPPING_BUILDINGS = 3804;
     40    protected static final int OVERLAPPING_BUILDING_RESIDENTIAL= 3805;
     41
     42    private List<Way> areaWays;
     43    private List<Relation> multipolygons;
     44    private String reason;
     45    private int code;
     46
     47    /** Constructor */
     48    public OverlappingAreas() {
     49        super(tr("Overlapping areas"),
     50                tr("This test checks if two areas intersect."));
     51    }
     52
     53    @Override
     54    public void startTest(ProgressMonitor monitor) {
     55        super.startTest(monitor);
     56        areaWays = new ArrayList<>();
     57        multipolygons = new ArrayList<>();
     58    }
     59
     60    @Override
     61    public void endTest() {
     62        // way-way overlaps
     63        for (int i = 0; i < areaWays.size(); i++) {
     64            Way w1 = areaWays.get(i);
     65            Area a1 = null;
     66            for (int j = i + 1; j < areaWays.size(); j++) {
     67                Way w2 = areaWays.get(j);
     68                resetErrorDetails();
     69                if (boundsIntersect(w1, w2) && !tagsAllowOverlap(w1, w2)) {
     70                    if (a1 == null) {
     71                        a1 = getEastNorthArea(w1);
     72                    }
     73                    checkIntersection(a1, getEastNorthArea(w2), w1, w2);
     74                }
     75            }
     76        }
     77        for (int i = 0; i < multipolygons.size(); i++) {
     78            Relation r1 = multipolygons.get(i);
     79            Area a1 = null;
     80            // multipolygon -way overlaps
     81            for (int j = 0; j < areaWays.size(); j++) {
     82                Way way = areaWays.get(j);
     83                resetErrorDetails();
     84                if (boundsIntersect(r1, way) && !tagsAllowOverlap(r1, way)) {
     85                    if (a1 == null) {
     86                        a1 = getEastNorthArea(r1);
     87                    }
     88                    checkIntersection(a1, getEastNorthArea(way), r1, way);
     89                }
     90            }
     91            for (int j = i+1; j < multipolygons.size(); j++) {
     92                Relation r2 = multipolygons.get(j);
     93                resetErrorDetails();
     94                if (boundsIntersect(r1, r2) && !tagsAllowOverlap(r1, r2)) {
     95                    if (a1 == null) {
     96                        a1 = getEastNorthArea(r1);
     97                    }
     98                    checkIntersection(a1, getEastNorthArea(r2), r1, r2);
     99                }
     100            }
     101
     102        }
     103        areaWays = null;
     104        multipolygons = null;
     105        super.endTest();
     106    }
     107
     108    private static boolean boundsIntersect(OsmPrimitive p1, OsmPrimitive p2) {
     109        return p1.getBBox().intersects(p2.getBBox());
     110    }
     111
     112    private static Area getEastNorthArea(OsmPrimitive p) {
     113        if (p instanceof Way) {
     114            return Geometry.getArea(((Way) p).getNodes());
     115        }
     116        Multipolygon mp = MultipolygonCache.getInstance().get((Relation) p);
     117        Path2D path = new Path2D.Double();
     118        path.setWindingRule(Path2D.WIND_EVEN_ODD);
     119        for (Multipolygon.PolyData pd : mp.getCombinedPolygons()) {
     120            Geometry.buildPath2DEastNorth(pd.getNodes(), path);
     121            for (Multipolygon.PolyData pdInner : pd.getInners()) {
     122                Geometry.buildPath2DEastNorth(pdInner.getNodes(), path);
     123            }
     124        }
     125        return new Area(path);
     126
     127    }
     128
     129    /**
     130     * Check intersection between two areas. If empty or extremely small ignore it, else add error
     131     * with highlighted area.
     132     * @param a1 the first area (EastNorth)
     133     * @param a2 the second area (EastNorth)
     134     * @param p1 primitive describing 1st polygon
     135     * @param p2 primitive describing 2nd polygon
     136     */
     137    private void checkIntersection(Area a1, Area a2, OsmPrimitive p1, OsmPrimitive p2) {
     138        Pair<PolygonIntersection, Area> pair = Geometry.polygonIntersectionResult(a1, a2, Geometry.INTERSECTION_EPS_EAST_NORTH);
     139        switch (pair.a) {
     140        case OUTSIDE:
     141            return;
     142        case FIRST_INSIDE_SECOND:
     143            if (code == OVERLAPPING_AREA || code == OVERLAPPING_BUILDING_RESIDENTIAL && isBuilding(p1))
     144                return;
     145            if (code == OVERLAPPING_WATER && p2.hasTag("natural","wetland"))
     146                return;
     147            break;
     148        case SECOND_INSIDE_FIRST:
     149            if (code == OVERLAPPING_AREA || code == OVERLAPPING_BUILDING_RESIDENTIAL && isBuilding(p2))
     150                return;
     151            if (code == OVERLAPPING_WATER && p1.hasTag("natural","wetland"))
     152                return;
     153            break;
     154        case CROSSING:
     155            break;
     156        }
     157
     158        // we have an intersection, calculate the elements to highlight
     159        PathIterator pit = pair.b.getPathIterator(null);
     160        double[] res = new double[6];
     161        List<List<Node>> hilite = new ArrayList<>();
     162        List<Node> nodes = new ArrayList<>();
     163        while (!pit.isDone()) {
     164            int type = pit.currentSegment(res);
     165            Node n = new Node(new EastNorth(res[0], res[1]));
     166            switch (type) {
     167            case PathIterator.SEG_MOVETO:
     168                if (!nodes.isEmpty()) {
     169                    hilite.add(nodes);
     170                }
     171                nodes = new ArrayList<>();
     172                nodes.add(n);
     173                break;
     174            case PathIterator.SEG_LINETO:
     175                nodes.add(n);
     176                break;
     177            case PathIterator.SEG_CLOSE:
     178                if (!nodes.isEmpty()) {
     179                    hilite.add(nodes);
     180                    nodes = new ArrayList<>();
     181                }
     182                break;
     183            default:
     184                break;
     185            }
     186            pit.next();
     187        }
     188        if (nodes.size() > 1) {
     189            hilite.add(nodes);
     190        }
     191
     192        switch (code) {
     193        case OVERLAPPING_WATER:
     194            reason = tr("Overlapping Water Areas");
     195            break;
     196        case OVERLAPPING_IDENTICAL_NATURAL:
     197            reason = tr("Overlapping identical natural areas");
     198            break;
     199        case OVERLAPPING_IDENTICAL_LANDLUSE:
     200            reason = tr("Overlapping identical landuses");
     201            break;
     202        case OVERLAPPING_BUILDINGS:
     203            reason = tr("Overlapping buildings");
     204            break;
     205        case OVERLAPPING_BUILDING_RESIDENTIAL:
     206            reason = tr("Overlapping building/residential area");
     207            break;
     208        default:
     209            reason = tr("Overlapping area");
     210
     211        }
     212        errors.add(TestError
     213                .builder(this, code == OVERLAPPING_AREA ? Severity.OTHER : Severity.WARNING, code)
     214                .message("OA: " + reason).primitives(p1, p2)
     215                .highlightNodePairs(hilite).build());
     216    }
     217
     218    /**
     219     * Check if the two objects are allowed to overlap regarding tags.
     220     * @param p1 1st primitive
     221     * @param p2 2nd primitive
     222     * @return true if the tags of the objects allow that the areas overlap.
     223     */
     224    private boolean tagsAllowOverlap(OsmPrimitive p1, OsmPrimitive p2) {
     225        if (isWaterArea(p1) && isWaterArea(p2)) {
     226            code = OVERLAPPING_WATER;
     227            return false;
     228        }
     229        if (isSetAndEqual(p1, p2, "natural")) {
     230            code = OVERLAPPING_IDENTICAL_NATURAL;
     231            return false;
     232        }
     233        if (isSetAndEqual(p1, p2, "landuse")) {
     234            code = OVERLAPPING_IDENTICAL_LANDLUSE;
     235            return false;
     236        }
     237        if (isBuilding(p1) && isBuilding(p2)) {
     238            code = OVERLAPPING_BUILDINGS;
     239            return false;
     240        }
     241        if (isBuilding(p1) && isResidentialArea(p2) || isBuilding(p2) && isResidentialArea(p1)) {
     242            code = OVERLAPPING_BUILDING_RESIDENTIAL;
     243            return false;
     244        }
     245        if (ValidatorPrefHelper.PREF_OTHER.get() && p1.concernsArea() && p2.concernsArea()) {
     246            code = OVERLAPPING_AREA;
     247            return false;
     248        }
     249        return true;
     250    }
     251
     252    private static boolean isWaterArea(OsmPrimitive p) {
     253        return p.hasTag("natural", "water", "wetland", "coastline") || p.hasTag("landuse","reservoir");
     254    }
     255
     256    private static boolean isSetAndEqual(OsmPrimitive p1, OsmPrimitive p2, String key) {
     257        String v1 = p1.get(key);
     258        String v2 = p2.get(key);
     259        return v1 != null && v1.equals(v2);
     260    }
     261
     262    private void resetErrorDetails() {
     263        reason = null;
     264        code = -1;
     265    }
     266
     267    @Override
     268    public void visit(Way w) {
     269        if (w.isArea()) {
     270            areaWays.add(w);
     271        }
     272    }
     273
     274    @Override
     275    public void visit(Relation r) {
     276        if (r.isMultipolygon()) {
     277            multipolygons.add(r);
     278        }
     279    }
     280}
  • src/org/openstreetmap/josm/tools/Geometry.java

     
    555555    }
    556556
    557557    /**
     558     * Builds a path from a list of nodes
     559     * @param polygon Nodes, forming a closed polygon
     560     * @param path2d path to add to; can be null, then a new path is created
     561     * @return the path (EastNorth coordinates)
     562     * @since xxx
     563     */
     564    public static Path2D buildPath2DEastNorth(List<Node> polygon, Path2D path2d) {
     565        Path2D path = path2d != null ? path2d : new Path2D.Double();
     566        boolean begin = true;
     567        for (INode n : polygon) {
     568            EastNorth en = n.getEastNorth();
     569            if (begin) {
     570                path.moveTo(en.getX(), en.getY());
     571                begin = false;
     572            } else {
     573                path.lineTo(en.getX(), en.getY());
     574            }
     575        }
     576        if (!begin) {
     577            path.closePath();
     578        }
     579        return path;
     580    }
     581
     582    /**
    558583     * Returns the Area of a polygon, from the multipolygon relation.
    559584     * @param multipolygon the multipolygon relation
    560585     * @return Area for the multipolygon (LatLon coordinates)
     
    603628     * @return intersection kind
    604629     */
    605630    public static PolygonIntersection polygonIntersection(Area a1, Area a2, double eps) {
     631        return polygonIntersectionResult(a1, a2, eps).a;
     632    }
    606633
     634    /**
     635     * Calculate intersection area and kind of intersection between two polygons.
     636     * @param a1 Area of first polygon
     637     * @param a2 Area of second polygon
     638     * @param eps an area threshold, everything below is considered an empty intersection
     639     * @return pair with intersection kind and intersection area (never null, but maybe empty)
     640     * @since xxx
     641     */
     642    public static Pair<PolygonIntersection, Area> polygonIntersectionResult(Area a1, Area a2, double eps) {
    607643        Area inter = new Area(a1);
    608644        inter.intersect(a2);
    609645
    610646        if (inter.isEmpty() || !checkIntersection(inter, eps)) {
    611             return PolygonIntersection.OUTSIDE;
     647            return new Pair<>(PolygonIntersection.OUTSIDE, inter);
    612648        } else if (a2.getBounds2D().contains(a1.getBounds2D()) && inter.equals(a1)) {
    613             return PolygonIntersection.FIRST_INSIDE_SECOND;
     649            return new Pair<>(PolygonIntersection.FIRST_INSIDE_SECOND, inter);
    614650        } else if (a1.getBounds2D().contains(a2.getBounds2D()) && inter.equals(a2)) {
    615             return PolygonIntersection.SECOND_INSIDE_FIRST;
     651            return new Pair<>(PolygonIntersection.SECOND_INSIDE_FIRST, inter);
    616652        } else {
    617             return PolygonIntersection.CROSSING;
     653            return new Pair<>(PolygonIntersection.CROSSING, inter);
    618654        }
    619655    }
    620656