/* * Copyright (C) 2012. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 or * version 2 as published by the Free Software Foundation. * * This program 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. */ package uk.me.parabola.mkgmap.reader.osm; import java.awt.Rectangle; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; import uk.me.parabola.imgfmt.app.Area; import uk.me.parabola.imgfmt.app.Coord; import uk.me.parabola.log.Logger; import uk.me.parabola.util.EnhancedProperties; /** * The hook removes all elements that will not be included in the map and can therefore * be safely removed. This improves the performance because the elements does not have * to go through the style system. * * @author WanMil */ public class UnusedElementsRemoverHook extends OsmReadingHooksAdaptor { private static final Logger log = Logger.getLogger(UnusedElementsRemoverHook.class); private ElementSaver saver; /** node with tags of this list must not be removed */ private Collection<String> nodeTagBlacklist; public UnusedElementsRemoverHook() { } public boolean init(ElementSaver saver, EnhancedProperties props) { this.saver = saver; // Get the tags from the POIGeneratorHook which are used to define the point // where the POI is placed in polygons. They must not be removed if the polygon // is not removed. Checking if the polygon is not removed is too costly therefore // all nodes with these tags are kept. nodeTagBlacklist = new HashSet<String>(); List<Entry<String,String>> areasToPoiNodeTags = POIGeneratorHook.getPoiPlacementTags(props); for (Entry<String,String> nodeTags : areasToPoiNodeTags) { nodeTagBlacklist.add(nodeTags.getKey()); } return true; } public void end() { long t1 = System.currentTimeMillis(); log.info("Removing unused elements"); final Area bbox = saver.getBoundingBox(); long nodes = saver.getNodes().size(); // go through all nodes for (Node node : new ArrayList<Node>(saver.getNodes().values())) { // nodes without tags can be removed if (node.getTagCount() == 0) { saver.getNodes().remove(node.getId()); continue; } // check if the node is within the tile bounding box if (bbox.contains(node.getLocation()) == false) { boolean removeNode = true; // check if the node has no tag of the blacklist if (nodeTagBlacklist.isEmpty() == false) { for (String tag : nodeTagBlacklist ) { if (node.getTag(tag) != null) { // the node contains one tag that might be interesting for the POIGeneratorHook // do not remove it removeNode = false; break; } } } if (removeNode) { saver.getNodes().remove(node.getId()); } else { log.debug("Keep node", node, "because it contains a tag which might be required for the area-to-poi function."); } } } long tr1 = System.currentTimeMillis(); // store all way ids that are referenced by a relation // all tags without a tag must not be removed if they are referenced by a relation Set<Long> relationWays = new HashSet<Long>(); for (Relation rel : saver.getRelations().values()) { for (Entry<String, Element> relEntry : rel.getElements()) { if (relEntry.getValue() instanceof Way) { relationWays.add(relEntry.getValue().getId()); } } } log.debug("Collecting way ids from relations took", (System.currentTimeMillis()-tr1), "ms"); Rectangle bboxRect = new Rectangle(bbox.getMinLong(), bbox.getMinLat(), bbox.getWidth(), bbox.getHeight()); long relWays = 0; long ways = saver.getWays().size(); for (Way way : new ArrayList<Way>(saver.getWays().values())) { if (way.getPoints().isEmpty()) { // empty way will not appear in the map => remove it saver.getWays().remove(way.getId()); continue; } // check if a way has no tags and is not a member of a relation // a relation might be used to add tags to the way using the style file if (way.getTagCount() == 0) { if (relationWays.contains(way.getId())) { relWays++; } else { saver.getWays().remove(way.getId()); continue; } } // check if the way is completely outside the tile bounding box boolean coordInBbox = false; Coord prevC = null; // It is possible that the way is larger than the bounding box and therefore // contains the bbox completely. Especially this is true for the sea polygon // when using --generate-sea=polygon // So need the calc the bbox of the way Coord firstC = way.getPoints().get(0); int minLat = firstC.getLatitude(); int maxLat = firstC.getLatitude(); int minLong = firstC.getLongitude(); int maxLong = firstC.getLongitude(); for (Coord c : way.getPoints()) { if (bbox.contains(c)) { coordInBbox = true; break; } else if (prevC != null) { // check if the line intersects the bounding box if (bboxRect.intersectsLine(prevC.getLongitude(), prevC.getLatitude(), c.getLongitude(), c.getLatitude())) { if (log.isDebugEnabled()) { log.debug("Intersection!"); log.debug("Bbox:", bbox); log.debug("Way coords:", prevC, c); } coordInBbox = true; break; } } if (minLat > c.getLatitude()) { minLat = c.getLatitude(); } else if (maxLat < c.getLatitude()) { maxLat = c.getLatitude(); } if (minLong > c.getLongitude()) { minLong = c.getLongitude(); } else if (maxLong < c.getLongitude()) { maxLong = c.getLongitude(); } prevC = c; } if (coordInBbox==false) { // no coord of the way is within the bounding box // check if the way possibly covers the bounding box completely Area wayBbox = new Area(minLat, minLong, maxLat, maxLong); if (wayBbox.intersects(saver.getBoundingBox())) { log.debug(way, "possibly covers the bbox completely. Keep it.", way.toTagString()); } else { saver.getWays().remove(way.getId()); } } } log.info("Relation referenced ways:", relationWays.size(), "Used:", relWays); log.info("Nodes: before:", nodes, "after:", saver.getNodes().size()); log.info("Ways: before:", ways, "after:", saver.getWays().size()); log.info("Removing unused elements took", (System.currentTimeMillis()-t1), "ms"); } }