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    }