Ticket #17011: 17011-POC.patch

File 17011-POC.patch, 8.9 KB (added by GerdP, 7 years ago)

Proof of concept regarding hilite, also finds intersections of multipolygons with ways or multipolygons. Only checks identical natural or landuse areas for now.

  • 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, // 3900 .. 3999
    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.Rectangle;
     7import java.awt.geom.Area;
     8import java.awt.geom.Path2D;
     9import java.awt.geom.PathIterator;
     10import java.util.ArrayList;
     11import java.util.Arrays;
     12import java.util.List;
     13
     14import org.openstreetmap.josm.data.coor.LatLon;
     15import org.openstreetmap.josm.data.osm.Node;
     16import org.openstreetmap.josm.data.osm.OsmPrimitive;
     17import org.openstreetmap.josm.data.osm.Relation;
     18import org.openstreetmap.josm.data.osm.Way;
     19import org.openstreetmap.josm.data.validation.Severity;
     20import org.openstreetmap.josm.data.validation.Test;
     21import org.openstreetmap.josm.data.validation.TestError;
     22import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     23import org.openstreetmap.josm.tools.Geometry;
     24
     25/**
     26 * Tests if there are overlapping areas
     27 *
     28 * @author Gerd Petermann
     29 * @since xxx
     30 */
     31public class OverlappingAreas extends Test {
     32    protected static final int OVERLAPPING_AREA = 3900;
     33    protected static final int OVERLAPPING_IDENTICAL_NATURAL = 3901;
     34    protected static final int OVERLAPPING_IDENTICAL_LANDLUSE = 3902;
     35
     36    private List<Way> areaWays;
     37    private List<Relation> multipolygons;
     38    private String reason;
     39    private int code;
     40
     41    /** Constructor */
     42    public OverlappingAreas() {
     43        super(tr("Overlapping areas"),
     44                tr("This test checks if two areas overlap."));
     45    }
     46
     47    @Override
     48    public void startTest(ProgressMonitor monitor) {
     49        super.startTest(monitor);
     50        areaWays = new ArrayList<>();
     51        multipolygons = new ArrayList<>();
     52    }
     53
     54    @Override
     55    public void endTest() {
     56        // way-way overlaps
     57        for (int i = 0; i < areaWays.size(); i++) {
     58            Way w1 = areaWays.get(i);
     59            Area a1 = null;
     60            for (int j = i + 1; j < areaWays.size(); j++) {
     61                Way w2 = areaWays.get(j);
     62                resetErrorDetails();
     63                if (!tagsAllowOverlap(w1, w2)) {
     64                    if (a1 == null) {
     65                        a1 = getLatLonArea(w1);
     66                    }
     67                    Area intersection = getLatLonArea(w2);
     68                    intersection.intersect(a1);
     69                    checkIntersection(intersection, Arrays.asList(w1, w2));
     70                }
     71            }
     72        }
     73        for (int i = 0; i < multipolygons.size(); i++) {
     74            Relation r1 = multipolygons.get(i);
     75            Area a1 = null;
     76            // multipolygon -way overlaps
     77            for (int j = 0; j < areaWays.size(); j++) {
     78                Way way = areaWays.get(j);
     79                resetErrorDetails();
     80                if (!tagsAllowOverlap(r1, way)) {
     81                    if (a1 == null) {
     82                        a1 = getLatLonArea(r1);
     83                    }
     84                    Area intersection = getLatLonArea(way);
     85                    intersection.intersect(a1);
     86                    checkIntersection(intersection, Arrays.asList(r1, way));
     87                }
     88            }
     89            for (int j = i+1; j < multipolygons.size(); j++) {
     90                Relation r2 = multipolygons.get(i);
     91                resetErrorDetails();
     92                if (!tagsAllowOverlap(r1, r2)) {
     93                    if (a1 == null) {
     94                        a1 = getLatLonArea(r1);
     95                    }
     96                    Area intersection = getLatLonArea(r2);
     97                    intersection.intersect(a1);
     98                    checkIntersection(intersection, Arrays.asList(r1, r2));
     99                }
     100            }
     101
     102        }
     103        areaWays = null;
     104        multipolygons = null;
     105        super.endTest();
     106    }
     107
     108    private static Area getLatLonArea(OsmPrimitive p) {
     109        if (p instanceof Way) {
     110            Path2D latLonPath = new Path2D.Double();
     111            Geometry.buildPath2DLatLon(((Way) p).getNodes(), latLonPath);
     112            return new Area(latLonPath);
     113        }
     114        return Geometry.getAreaLatLon((Relation) p);
     115    }
     116
     117    /**
     118     * Check intersection area. If empty or extremely small ignore it, else add error
     119     * with highlighted area.
     120     * @param inter the area (LatLon)
     121     * @param primitives the primitives that build the areas which possibly intersect
     122     */
     123    private void checkIntersection(Area inter, List<OsmPrimitive> primitives) {
     124        Rectangle bounds = inter.getBounds();
     125        if (inter.isEmpty() || bounds.getHeight() * bounds.getWidth() <= 1e-6) {
     126            return;
     127        }
     128
     129        PathIterator pit = inter.getPathIterator(null);
     130        double[] res = new double[6];
     131        List<List<Node>> hilite = new ArrayList<>();
     132        List<Node> nodes = new ArrayList<>();
     133        while (!pit.isDone()) {
     134            int type = pit.currentSegment(res);
     135            Node n = new Node(new LatLon(res[1], res[0]));
     136            switch (type) {
     137            case PathIterator.SEG_MOVETO:
     138                if (!nodes.isEmpty()) {
     139                    hilite.add(nodes);
     140                }
     141                nodes = new ArrayList<>();
     142                nodes.add(n);
     143                break;
     144            case PathIterator.SEG_LINETO:
     145                nodes.add(n);
     146                break;
     147            case PathIterator.SEG_CLOSE:
     148                if (!nodes.isEmpty()) {
     149                    nodes.add(nodes.get(0));
     150                    hilite.add(nodes);
     151                    nodes = new ArrayList<>();
     152                }
     153                break;
     154            default:
     155                break;
     156            }
     157            pit.next();
     158        }
     159        if (nodes.size() > 1) {
     160            hilite.add(nodes);
     161        }
     162        errors.add(TestError.builder(this, Severity.WARNING, code < 0 ? OVERLAPPING_AREA : code)
     163                .message(reason == null ? tr("OA: Overlapping areas") : reason)
     164                .primitives(primitives)
     165                .highlightNodePairs(hilite)
     166                .build());
     167    }
     168
     169    /**
     170     * Check if the two objects are allowed to overlap regarding tags.
     171     * @param p1 1st primitive
     172     * @param p2 2nd primitive
     173     * @return true if the tags of the objects allow that the areas overlap.
     174     */
     175    private boolean tagsAllowOverlap(OsmPrimitive p1, OsmPrimitive p2) {
     176        // TODO: read from config file
     177        if (isSetAndEqual(p1, p2, "natural")) {
     178            code = OVERLAPPING_IDENTICAL_NATURAL;
     179            return false;
     180        }
     181        if (isSetAndEqual(p1, p2, "landuse")) {
     182            code = OVERLAPPING_IDENTICAL_LANDLUSE;
     183            return false;
     184        }
     185        return true;
     186    }
     187
     188    private boolean isSetAndEqual(OsmPrimitive p1, OsmPrimitive p2, String key) {
     189        String v1 = p1.get(key);
     190        String v2 = p2.get(key);
     191        if (v1 != null && v1.equals(v2)) {
     192            reason = tr("OA: Overlapping identical {0} areas", key);
     193            return true;
     194        }
     195        return false;
     196    }
     197
     198    private void resetErrorDetails() {
     199        reason = null;
     200        code = -1;
     201    }
     202
     203    @Override
     204    public void visit(Way w) {
     205        if (w.isArea() && w.hasAreaTags()) {
     206            areaWays.add(w);
     207        }
     208    }
     209
     210    @Override
     211    public void visit(Relation r) {
     212        if (r.isMultipolygon()) {
     213            multipolygons.add(r);
     214        }
     215    }
     216}