/************************************************************************** OSMemory library for OSM data processing. Copyright (C) 2014 Aleś Bułojčyk <alex73mail@gmail.com> This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. **************************************************************************/ package org.alex73.osmemory.geometry; import java.util.HashSet; import java.util.Set; import org.alex73.osmemory.IOsmNode; import org.alex73.osmemory.IOsmObject; import org.alex73.osmemory.IOsmRelation; import org.alex73.osmemory.IOsmWay; import org.alex73.osmemory.MemoryStorage; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.Point; /** * Polygon.contains() call performance optimization. It can be used for checks like "if node inside country", * "if way inside city", etc. * * This class creates 400(by default) cells above of polygon as cache. Each cell can have 3 states: fully * included into polygon, not included in polygon, included partially. For the fully included and not included * cells checking is very fast operation. For partially included - it checks only subpolygon inside cell, that * is also a little bit faster process. * * Keep in mind, that only nodes inside polygon will be checked. That means, if you have way from upper left * corner into bottom right corner, i.e. overlaps polygon, but there is no nodes of way inside polygon, then * this way will be treated as 'not contains'. * * 'Area contains node' means node should be inside area, but not on the border. That means border's nodes are * not contained in area. * * 'Area covers node' means node should be inside area or on the border. */ public class FastArea extends Fast { private final MemoryStorage storage; public FastArea(Geometry polygon, MemoryStorage storage) { super(polygon); if (storage == null) { throw new IllegalArgumentException(); } this.storage = storage; } public FastArea(IOsmObject areaObject, MemoryStorage storage) { this(OsmHelper.areaFromObject(areaObject, storage), storage); } public boolean mayCovers(BoundingBox box) { if (maxx < box.minLon) { return false; } if (minx > box.maxLon) { return false; } if (maxy < box.minLat) { return false; } if (miny > box.maxLat) { return false; } return true; } public boolean covers(IOsmObject obj) { switch (obj.getType()) { case IOsmObject.TYPE_NODE: return coversNode((IOsmNode) obj); case IOsmObject.TYPE_WAY: return coversWay((IOsmWay) obj); case IOsmObject.TYPE_RELATION: return coversRelation((IOsmRelation) obj, new HashSet<>()); default: throw new RuntimeException("Unknown object type"); } } public boolean covers(IExtendedObject ext) { if (!mayCovers(ext.getBoundingBox())) { return false; } // iterate by full cells first - it will be faster than check inside geometry Boolean covers = ext.iterateNodes(new IExtendedObject.NodesIterator() { public Boolean processNode(IOsmNode node) { if (node == null) { return null; } if (isSkipped(node)) { return null; } Cell c = getCellForPoint(node.getLat(), node.getLon()); if (c != null && c.isFull()) { return true; } return null; } }); if (covers != null) { return covers; } // iterate by geometries because there are no points inside full cells covers = ext.iterateNodes(new IExtendedObject.NodesIterator() { public Boolean processNode(IOsmNode node) { if (node == null) { return null; } if (isSkipped(node)) { return null; } Cell c = getCellForPoint(node.getLat(), node.getLon()); if (c != null && !c.isFull() && coversCellNode(c, node)) { return true; } return null; } }); if (covers != null) { return covers; } return false; } /** * Return true if node should be used for covers tests. May be useful for check borders. */ protected boolean isSkipped(IOsmNode node) { return false; } protected boolean coversNode(IOsmNode node) { Cell c = getCellForPoint(node.getLat(), node.getLon()); if (c == null) { return false; } if (c.isEmpty()) { return false; } else if (c.isFull()) { return true; } else { return coversCellNode(c, node); } } protected boolean coversCellNode(Cell c, IOsmNode node) { Point p = GeometryHelper.createPoint(node.getLongitude(), node.getLatitude()); if (c.getGeom() instanceof GeometryCollection) { // cached can be collection of point and polygon, but covers doesn't work with collection GeometryCollection cachedCollection = (GeometryCollection) c.getGeom(); for (int i = 0; i < cachedCollection.getNumGeometries(); i++) { if (cachedCollection.getGeometryN(i).contains(p)) { return true; } } return false; } else { return c.getGeom().covers(p); } } protected boolean coversWay(IOsmWay way) { return covers(new ExtendedWay(way, storage)); } protected boolean coversRelation(IOsmRelation rel, Set<String> processedRelations) { processedRelations.add(rel.getObjectCode()); for (int i = 0; i < rel.getMembersCount(); i++) { IOsmObject o = rel.getMemberObject(storage, i); if (o == null) { continue; } if (o.isRelation()) { if (processedRelations.contains(o.getObjectCode())) { // check against circular relations continue; } else if (coversRelation((IOsmRelation) o, processedRelations)) { return true; } } else if (covers(o)) { return true; } } return false; } }