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 }