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    }