Ticket #17011: 17011-v3.patch

File 17011-v3.patch, 16.2 KB (added by GerdP, 6 years ago)
  • check layer tag
  • 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/Test.java

     
    1818import org.openstreetmap.josm.command.DeleteCommand;
    1919import org.openstreetmap.josm.data.osm.Node;
    2020import org.openstreetmap.josm.data.osm.OsmPrimitive;
     21import org.openstreetmap.josm.data.osm.OsmUtils;
    2122import org.openstreetmap.josm.data.osm.Relation;
    2223import org.openstreetmap.josm.data.osm.Way;
    2324import org.openstreetmap.josm.data.osm.search.SearchCompiler.InDataSourceArea;
     
    364365        return p.hasTag("landuse", "residential");
    365366    }
    366367
     368    /**
     369     * Determines if the specified primitives are in the same layer.
     370     * @param p1 first primitive
     371     * @param p2 second primitive
     372     * @return True if the objects are in the same layer
     373     */
     374    protected static final boolean inSameLayer(OsmPrimitive p1, OsmPrimitive p2) {
     375        return Objects.equals(OsmUtils.getLayer(p1), OsmUtils.getLayer(p2));
     376    }
     377
    367378    @Override
    368379    public int hashCode() {
    369380        return Objects.hash(name, description);
  • src/org/openstreetmap/josm/data/validation/tests/CrossingWays.java

     
    1313
    1414import org.openstreetmap.josm.data.coor.EastNorth;
    1515import org.openstreetmap.josm.data.osm.OsmPrimitive;
    16 import org.openstreetmap.josm.data.osm.OsmUtils;
    1716import org.openstreetmap.josm.data.osm.Relation;
    1817import org.openstreetmap.josm.data.osm.Way;
    1918import org.openstreetmap.josm.data.osm.WaySegment;
     
    106105        boolean ignoreWaySegmentCombination(Way w1, Way w2) {
    107106            if (w1 == w2)
    108107                return false;
    109             if (!Objects.equals(OsmUtils.getLayer(w1), OsmUtils.getLayer(w2))) {
     108            if (!inSameLayer(w1, w2)) {
    110109                return true;
    111110            }
    112111            if (w1.hasKey(HIGHWAY) && w2.hasKey(HIGHWAY) && !Objects.equals(w1.get("level"), w2.get("level"))) {
     
    251250
    252251        @Override
    253252        boolean ignoreWaySegmentCombination(Way w1, Way w2) {
    254             return !Objects.equals(OsmUtils.getLayer(w1), OsmUtils.getLayer(w2));
     253            return !inSameLayer(w1, w2);
    255254        }
    256255
    257256    }
  • 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 in the same layer."));
     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 (Way way : areaWays) {
     82                if (way.referrers(Relation.class).anyMatch(parent -> parent == r1)) {
     83                    // ignore members of multipolygon
     84                    continue;
     85                }
     86                resetErrorDetails();
     87                if (boundsIntersect(r1, way) && !tagsAllowOverlap(r1, way)) {
     88                    if (a1 == null) {
     89                        a1 = getEastNorthArea(r1);
     90                    }
     91                    checkIntersection(a1, getEastNorthArea(way), r1, way);
     92                }
     93            }
     94            for (int j = i+1; j < multipolygons.size(); j++) {
     95                Relation r2 = multipolygons.get(j);
     96                resetErrorDetails();
     97                if (boundsIntersect(r1, r2) && !tagsAllowOverlap(r1, r2)) {
     98                    if (a1 == null) {
     99                        a1 = getEastNorthArea(r1);
     100                    }
     101                    checkIntersection(a1, getEastNorthArea(r2), r1, r2);
     102                }
     103            }
     104
     105        }
     106        // clean up memory
     107        areaWays = null;
     108        multipolygons = null;
     109        super.endTest();
     110    }
     111
     112    private static boolean boundsIntersect(OsmPrimitive p1, OsmPrimitive p2) {
     113        return p1.getBBox().intersects(p2.getBBox());
     114    }
     115
     116    private static Area getEastNorthArea(OsmPrimitive p) {
     117        if (p instanceof Way) {
     118            return Geometry.getArea(((Way) p).getNodes());
     119        }
     120        Multipolygon mp = MultipolygonCache.getInstance().get((Relation) p);
     121        Path2D path = new Path2D.Double();
     122        path.setWindingRule(Path2D.WIND_EVEN_ODD);
     123        for (Multipolygon.PolyData pd : mp.getCombinedPolygons()) {
     124            path.append(pd.get(), false);
     125        }
     126        return new Area(path);
     127
     128    }
     129
     130    /**
     131     * Check intersection between two areas. If empty or extremely small ignore it, else add error
     132     * with highlighted area.
     133     * @param a1 the first area (EastNorth)
     134     * @param a2 the second area (EastNorth)
     135     * @param p1 primitive describing 1st polygon
     136     * @param p2 primitive describing 2nd polygon
     137     */
     138    private void checkIntersection(Area a1, Area a2, OsmPrimitive p1, OsmPrimitive p2) {
     139        Pair<PolygonIntersection, Area> pair = Geometry.polygonIntersectionResult(a1, a2, Geometry.INTERSECTION_EPS_EAST_NORTH);
     140        switch (pair.a) {
     141        case OUTSIDE:
     142            return;
     143        case FIRST_INSIDE_SECOND:
     144            if (code == OVERLAPPING_AREA || code == OVERLAPPING_BUILDING_RESIDENTIAL && isBuilding(p1))
     145                return;
     146            if (code == OVERLAPPING_WATER && p2.hasTag("natural","wetland"))
     147                return;
     148            break;
     149        case SECOND_INSIDE_FIRST:
     150            if (code == OVERLAPPING_AREA || code == OVERLAPPING_BUILDING_RESIDENTIAL && isBuilding(p2))
     151                return;
     152            if (code == OVERLAPPING_WATER && p1.hasTag("natural","wetland"))
     153                return;
     154            break;
     155        case CROSSING:
     156            break;
     157        }
     158
     159        // we have an intersection, calculate the elements to highlight
     160        PathIterator pit = pair.b.getPathIterator(null);
     161        double[] res = new double[6];
     162        List<List<Node>> hilite = new ArrayList<>();
     163        List<Node> nodes = new ArrayList<>();
     164        while (!pit.isDone()) {
     165            int type = pit.currentSegment(res);
     166            Node n = new Node(new EastNorth(res[0], res[1]));
     167            switch (type) {
     168            case PathIterator.SEG_MOVETO:
     169                if (!nodes.isEmpty()) {
     170                    hilite.add(nodes);
     171                }
     172                nodes = new ArrayList<>();
     173                nodes.add(n);
     174                break;
     175            case PathIterator.SEG_LINETO:
     176                nodes.add(n);
     177                break;
     178            case PathIterator.SEG_CLOSE:
     179                if (!nodes.isEmpty()) {
     180                    hilite.add(nodes);
     181                    nodes = new ArrayList<>();
     182                }
     183                break;
     184            default:
     185                break;
     186            }
     187            pit.next();
     188        }
     189        if (nodes.size() > 1) {
     190            hilite.add(nodes);
     191        }
     192
     193        switch (code) {
     194        case OVERLAPPING_WATER:
     195            reason = tr("Overlapping Water Areas");
     196            break;
     197        case OVERLAPPING_IDENTICAL_NATURAL:
     198            reason = tr("Overlapping identical natural areas");
     199            break;
     200        case OVERLAPPING_IDENTICAL_LANDLUSE:
     201            reason = tr("Overlapping identical landuses");
     202            break;
     203        case OVERLAPPING_BUILDINGS:
     204            if (pair.a == PolygonIntersection.CROSSING)
     205                reason = tr("Overlapping buildings");
     206            else
     207                reason = tr("Building inside building");
     208            break;
     209        case OVERLAPPING_BUILDING_RESIDENTIAL:
     210            reason = tr("Overlapping building/residential area");
     211            break;
     212        default:
     213            reason = tr("Overlapping area");
     214
     215        }
     216        errors.add(TestError
     217                .builder(this, code == OVERLAPPING_AREA ? Severity.OTHER : Severity.WARNING, code)
     218                .message("OA: " + reason).primitives(p1, p2)
     219                .highlightNodePairs(hilite).build());
     220    }
     221
     222    /**
     223     * Check if the two objects are allowed to overlap regarding tags.
     224     * @param p1 1st primitive
     225     * @param p2 2nd primitive
     226     * @return true if the tags of the objects allow that the areas overlap.
     227     */
     228    private boolean tagsAllowOverlap(OsmPrimitive p1, OsmPrimitive p2) {
     229        if (!inSameLayer(p1, p2)) {
     230            return true;
     231        }
     232        if (isWaterArea(p1) && isWaterArea(p2)) {
     233            code = OVERLAPPING_WATER;
     234            return false;
     235        }
     236        if (isSetAndEqual(p1, p2, "natural")) {
     237            code = OVERLAPPING_IDENTICAL_NATURAL;
     238            return false;
     239        }
     240        if (isSetAndEqual(p1, p2, "landuse")) {
     241            code = OVERLAPPING_IDENTICAL_LANDLUSE;
     242            return false;
     243        }
     244        if (isBuilding(p1) && isBuilding(p2)) {
     245            code = OVERLAPPING_BUILDINGS;
     246            return false;
     247        }
     248        if (isBuilding(p1) && isResidentialArea(p2) || isBuilding(p2) && isResidentialArea(p1)) {
     249            code = OVERLAPPING_BUILDING_RESIDENTIAL;
     250            return false;
     251        }
     252        if (ValidatorPrefHelper.PREF_OTHER.get() && p1.concernsArea() && p2.concernsArea()) {
     253            code = OVERLAPPING_AREA;
     254            return false;
     255        }
     256        return true;
     257    }
     258
     259    private static boolean isWaterArea(OsmPrimitive p) {
     260        return p.hasTag("natural", "water", "wetland") || p.hasTag("landuse","reservoir");
     261    }
     262
     263    private static boolean isSetAndEqual(OsmPrimitive p1, OsmPrimitive p2, String key) {
     264        String v1 = p1.get(key);
     265        String v2 = p2.get(key);
     266        return v1 != null && v1.equals(v2);
     267    }
     268
     269    private void resetErrorDetails() {
     270        reason = null;
     271        code = -1;
     272    }
     273
     274    @Override
     275    public void visit(Way w) {
     276        if (w.isArea()) {
     277            areaWays.add(w);
     278        }
     279    }
     280
     281    @Override
     282    public void visit(Relation r) {
     283        if (r.isMultipolygon()) {
     284            multipolygons.add(r);
     285        }
     286    }
     287}
  • src/org/openstreetmap/josm/tools/Geometry.java

     
    603603     * @return intersection kind
    604604     */
    605605    public static PolygonIntersection polygonIntersection(Area a1, Area a2, double eps) {
     606        return polygonIntersectionResult(a1, a2, eps).a;
     607    }
    606608
     609    /**
     610     * Calculate intersection area and kind of intersection between two polygons.
     611     * @param a1 Area of first polygon
     612     * @param a2 Area of second polygon
     613     * @param eps an area threshold, everything below is considered an empty intersection
     614     * @return pair with intersection kind and intersection area (never null, but maybe empty)
     615     * @since xxx
     616     */
     617    public static Pair<PolygonIntersection, Area> polygonIntersectionResult(Area a1, Area a2, double eps) {
    607618        Area inter = new Area(a1);
    608619        inter.intersect(a2);
    609620
    610621        if (inter.isEmpty() || !checkIntersection(inter, eps)) {
    611             return PolygonIntersection.OUTSIDE;
     622            return new Pair<>(PolygonIntersection.OUTSIDE, inter);
    612623        } else if (a2.getBounds2D().contains(a1.getBounds2D()) && inter.equals(a1)) {
    613             return PolygonIntersection.FIRST_INSIDE_SECOND;
     624            return new Pair<>(PolygonIntersection.FIRST_INSIDE_SECOND, inter);
    614625        } else if (a1.getBounds2D().contains(a2.getBounds2D()) && inter.equals(a2)) {
    615             return PolygonIntersection.SECOND_INSIDE_FIRST;
     626            return new Pair<>(PolygonIntersection.SECOND_INSIDE_FIRST, inter);
    616627        } else {
    617             return PolygonIntersection.CROSSING;
     628            return new Pair<>(PolygonIntersection.CROSSING, inter);
    618629        }
    619630    }
    620631