001 // License: GPL. See LICENSE file for details. 002 package org.openstreetmap.josm.data.validation.tests; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.util.Collection; 007 import java.util.HashSet; 008 import java.util.LinkedList; 009 import java.util.List; 010 import java.util.Set; 011 012 import org.openstreetmap.josm.data.osm.Node; 013 import org.openstreetmap.josm.data.osm.OsmPrimitive; 014 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 015 import org.openstreetmap.josm.data.osm.QuadBuckets; 016 import org.openstreetmap.josm.data.osm.Relation; 017 import org.openstreetmap.josm.data.osm.RelationMember; 018 import org.openstreetmap.josm.data.osm.Way; 019 import org.openstreetmap.josm.data.validation.Severity; 020 import org.openstreetmap.josm.data.validation.Test; 021 import org.openstreetmap.josm.data.validation.TestError; 022 import org.openstreetmap.josm.tools.FilteredCollection; 023 import org.openstreetmap.josm.tools.Geometry; 024 import org.openstreetmap.josm.tools.Geometry.PolygonIntersection; 025 import org.openstreetmap.josm.tools.Predicate; 026 027 public class BuildingInBuilding extends Test { 028 029 protected static final int BUILDING_INSIDE_BUILDING = 2001; 030 protected List<OsmPrimitive> primitivesToCheck = new LinkedList<OsmPrimitive>(); 031 protected QuadBuckets<Way> index = new QuadBuckets<Way>(); 032 033 public BuildingInBuilding() { 034 super(tr("Building inside building"), tr("Checks for building areas inside of buildings.")); 035 } 036 037 @Override 038 public void visit(Node n) { 039 if (n.isUsable() && isBuilding(n)) { 040 primitivesToCheck.add(n); 041 } 042 } 043 044 @Override 045 public void visit(Way w) { 046 if (w.isUsable() && w.isClosed() && isBuilding(w)) { 047 primitivesToCheck.add(w); 048 index.add(w); 049 } 050 } 051 052 @Override 053 public void visit(Relation r) { 054 if (r.isUsable() && r.isMultipolygon() && isBuilding(r)) { 055 primitivesToCheck.add(r); 056 for (RelationMember m : r.getMembers()) { 057 if (m.getRole().equals("outer") && m.getType().equals(OsmPrimitiveType.WAY)) { 058 index.add(m.getWay()); 059 } 060 } 061 } 062 } 063 064 private static boolean isInPolygon(Node n, List<Node> polygon) { 065 return Geometry.nodeInsidePolygon(n, polygon); 066 } 067 068 protected class MultiPolygonMembers { 069 public final Set<Way> outers = new HashSet<Way>(); 070 public final Set<Way> inners = new HashSet<Way>(); 071 public MultiPolygonMembers(Relation multiPolygon) { 072 for (RelationMember m : multiPolygon.getMembers()) { 073 if (m.getType().equals(OsmPrimitiveType.WAY)) { 074 if (m.getRole().equals("outer")) { 075 outers.add(m.getWay()); 076 } else if (m.getRole().equals("inner")) { 077 inners.add(m.getWay()); 078 } 079 } 080 } 081 } 082 } 083 084 protected boolean sameLayers(Way w1, Way w2) { 085 String l1 = w1.get("layer") != null ? w1.get("layer") : "0"; 086 String l2 = w2.get("layer") != null ? w2.get("layer") : "0"; 087 return l1.equals(l2); 088 } 089 090 protected boolean isWayInsideMultiPolygon(Way object, Relation multiPolygon) { 091 // Extract outer/inner members from multipolygon 092 MultiPolygonMembers mpm = new MultiPolygonMembers(multiPolygon); 093 // Test if object is inside an outer member 094 for (Way out : mpm.outers) { 095 PolygonIntersection inter = Geometry.polygonIntersection(object.getNodes(), out.getNodes()); 096 if (inter == PolygonIntersection.FIRST_INSIDE_SECOND || inter == PolygonIntersection.CROSSING) { 097 boolean insideInner = false; 098 // If inside an outer, check it is not inside an inner 099 for (Way in : mpm.inners) { 100 if (Geometry.polygonIntersection(in.getNodes(), out.getNodes()) == PolygonIntersection.FIRST_INSIDE_SECOND && 101 Geometry.polygonIntersection(object.getNodes(), in.getNodes()) == PolygonIntersection.FIRST_INSIDE_SECOND) { 102 insideInner = true; 103 break; 104 } 105 } 106 // Inside outer but not inside inner -> the building appears to be inside a buiding 107 if (!insideInner) { 108 // Final check on "layer" tag. Buildings of different layers may be superposed 109 if (sameLayers(object, out)) { 110 return true; 111 } 112 } 113 } 114 } 115 return false; 116 } 117 118 @Override 119 public void endTest() { 120 for (final OsmPrimitive p : primitivesToCheck) { 121 Collection<Way> outers = new FilteredCollection<Way>(index.search(p.getBBox()), new Predicate<Way>() { 122 123 protected boolean evaluateNode(Node n, Way object) { 124 return isInPolygon(n, object.getNodes()) || object.getNodes().contains(n); 125 } 126 127 protected boolean evaluateWay(Way w, Way object) { 128 if (w.equals(object)) return false; 129 130 // Get all multipolygons referencing object 131 Collection<OsmPrimitive> buildingMultiPolygons = new FilteredCollection<OsmPrimitive>(object.getReferrers(), new Predicate<OsmPrimitive>() { 132 @Override 133 public boolean evaluate(OsmPrimitive object) { 134 return primitivesToCheck.contains(object); 135 } 136 }) ; 137 138 // if there's none, test if w is inside object 139 if (buildingMultiPolygons.isEmpty()) { 140 PolygonIntersection inter = Geometry.polygonIntersection(w.getNodes(), object.getNodes()); 141 // Final check on "layer" tag. Buildings of different layers may be superposed 142 return (inter == PolygonIntersection.FIRST_INSIDE_SECOND || inter == PolygonIntersection.CROSSING) && sameLayers(w, object); 143 } else { 144 // Else, test if w is inside one of the multipolygons 145 for (OsmPrimitive bmp : buildingMultiPolygons) { 146 if (bmp instanceof Relation) { 147 if (isWayInsideMultiPolygon(w, (Relation) bmp)) { 148 return true; 149 } 150 } 151 } 152 return false; 153 } 154 } 155 156 protected boolean evaluateRelation(Relation r, Way object) { 157 MultiPolygonMembers mpm = new MultiPolygonMembers((Relation) p); 158 for (Way out : mpm.outers) { 159 if (evaluateWay(out, object)) { 160 return true; 161 } 162 } 163 return false; 164 } 165 166 @Override 167 public boolean evaluate(Way object) { 168 if (p.equals(object)) 169 return false; 170 else if (p instanceof Node) 171 return evaluateNode((Node) p, object); 172 else if (p instanceof Way) 173 return evaluateWay((Way) p, object); 174 else if (p instanceof Relation) 175 return evaluateRelation((Relation) p, object); 176 return false; 177 } 178 }); 179 180 if (!outers.isEmpty()) { 181 errors.add(new TestError(this, Severity.WARNING, 182 tr("Building inside building"), BUILDING_INSIDE_BUILDING, p)); 183 } 184 } 185 186 super.endTest(); 187 } 188 }