Subversion Repositories mkgmap

Rev

Rev 3408 | View as "text/plain" | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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");
        }
}