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.LinkedList; 008 import java.util.List; 009 010 import org.openstreetmap.josm.data.coor.LatLon; 011 import org.openstreetmap.josm.data.osm.Node; 012 import org.openstreetmap.josm.data.osm.OsmPrimitive; 013 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 014 import org.openstreetmap.josm.data.osm.QuadBuckets; 015 import org.openstreetmap.josm.data.osm.Relation; 016 import org.openstreetmap.josm.data.osm.RelationMember; 017 import org.openstreetmap.josm.data.osm.Way; 018 import org.openstreetmap.josm.data.validation.Severity; 019 import org.openstreetmap.josm.data.validation.Test; 020 import org.openstreetmap.josm.data.validation.TestError; 021 import org.openstreetmap.josm.tools.FilteredCollection; 022 import org.openstreetmap.josm.tools.Geometry; 023 import org.openstreetmap.josm.tools.Predicate; 024 025 public class BuildingInBuilding extends Test { 026 027 protected static final int BUILDING_INSIDE_BUILDING = 2001; 028 protected List<OsmPrimitive> primitivesToCheck = new LinkedList<OsmPrimitive>(); 029 protected QuadBuckets<Way> index = new QuadBuckets<Way>(); 030 031 public BuildingInBuilding() { 032 super(tr("Building inside building"), tr("Checks for building areas inside of buildings.")); 033 } 034 035 @Override 036 public void visit(Node n) { 037 if (n.isUsable() && isBuilding(n)) { 038 primitivesToCheck.add(n); 039 } 040 } 041 042 @Override 043 public void visit(Way w) { 044 if (w.isUsable() && w.isClosed() && isBuilding(w)) { 045 primitivesToCheck.add(w); 046 index.add(w); 047 } 048 } 049 050 private static boolean isInPolygon(Node n, List<Node> polygon) { 051 return Geometry.nodeInsidePolygon(n, polygon); 052 } 053 054 /** 055 * Return true if w is in polygon. 056 */ 057 private static boolean isInPolygon(Way w, List<Node> polygon) { 058 // Check that all nodes of w are in polygon 059 for (Node n : w.getNodes()) { 060 if (!isInPolygon(n, polygon)) 061 return false; 062 } 063 // All nodes can be inside polygon and still, w outside: 064 // +-------------+ 065 // /| | 066 // / | | 067 // / | | 068 // / w | | 069 // +----+----+ | 070 // | polygon | 071 // |_______________________| 072 // 073 for (int i=1; i<w.getNodesCount(); i++) { 074 LatLon center = w.getNode(i).getCoor().getCenter(w.getNode(i-1).getCoor()); 075 if (center != null && !isInPolygon(new Node(center), polygon)) 076 return false; 077 } 078 return true; 079 } 080 081 @Override 082 public void endTest() { 083 for (final OsmPrimitive p : primitivesToCheck) { 084 Collection<Way> outers = new FilteredCollection<Way>(index.search(p.getBBox()), new Predicate<Way>() { 085 @Override 086 public boolean evaluate(Way object) { 087 if (p.equals(object)) 088 return false; 089 else if (p instanceof Node) 090 return isInPolygon((Node) p, object.getNodes()) || object.getNodes().contains(p); 091 else if (p instanceof Way) 092 return isInPolygon((Way) p, object.getNodes()) && !isInInnerWay((Way)p, object); 093 else 094 return false; 095 } 096 }); 097 if (!outers.isEmpty()) { 098 errors.add(new TestError(this, Severity.WARNING, 099 tr("Building inside building"), BUILDING_INSIDE_BUILDING, p)); 100 } 101 } 102 103 super.endTest(); 104 } 105 106 private boolean isInInnerWay(Way w, Way outer) { 107 for (OsmPrimitive r : outer.getReferrers()) { 108 if (r instanceof Relation && ((Relation)r).isMultipolygon()) { 109 for (RelationMember m : ((Relation)r).getMembers()) { 110 if (m.hasRole() && m.getRole().equals("inner") && m.getType().equals(OsmPrimitiveType.WAY)) { 111 // Only check inner ways actually inside the current outer 112 Way inner = m.getWay(); 113 if (isInPolygon(inner, outer.getNodes())) { 114 // If the tested way is inside this inner, outer is a false positive 115 if (isInPolygon(w, inner.getNodes())) 116 return true; 117 } 118 } 119 } 120 } 121 } 122 return false; 123 } 124 }