/* * Copyright (C) 2011 - 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.util.Iterator; import java.util.List; import java.util.Map.Entry; import uk.me.parabola.imgfmt.app.Coord; import uk.me.parabola.log.Logger; import uk.me.parabola.mkgmap.reader.osm.boundary.BoundaryGrid; import uk.me.parabola.mkgmap.reader.osm.boundary.BoundaryQuadTree; import uk.me.parabola.mkgmap.reader.osm.boundary.BoundaryUtil; import uk.me.parabola.util.EnhancedProperties; public class LocationHook extends OsmReadingHooksAdaptor { private static final Logger log = Logger.getLogger(LocationHook.class); // the resulting assignments are logged with this extra logger // so that it is possible to log only the results of the location hook private static final Logger resultLog = Logger.getLogger(LocationHook.class.getName()+".results"); // counters for stats private long cntQTSearch = 0; private long cntNotFnd = 0; private long cntwayNotFnd = 0; private BoundaryGrid boundaryGrid; private ElementSaver saver; private String boundaryDirName; /** this static object is used to synchronize the check if the bounds directory contains any bounds */ private static final Object BOUNDS_CHECK_LOCK = new Object(); /** Stores the name of the bounds dir/file that has been checked. Static so that multiple threads can access. */ private static String checkedBoundaryDirName; /** stores the result of the bounds dir/file check */ private static boolean checkBoundaryDirOk; private EnhancedProperties props; public boolean init(ElementSaver saver, EnhancedProperties props) { boundaryDirName = props.getProperty("bounds"); if (boundaryDirName == null) { // bounds property not set return false; } this.props = props; this.saver = saver; long t1 = System.currentTimeMillis(); synchronized (BOUNDS_CHECK_LOCK) { // checking of the boundary dir is expensive // check once only and reuse the result if (boundaryDirName.equals(checkedBoundaryDirName)) { if (checkBoundaryDirOk == false) { log.error("Disable LocationHook because bounds directory is unusable. Dir: "+boundaryDirName); return false; } } else { checkedBoundaryDirName = boundaryDirName; checkBoundaryDirOk = false; List<String> boundaryFiles = BoundaryUtil.getBoundaryDirContent(boundaryDirName); if (boundaryFiles == null || boundaryFiles.size() == 0) { log.error("LocationHook is disabled because no bounds files are available. Dir: " + boundaryDirName); return false; } // passed all checks => boundaries are okay checkBoundaryDirOk = true; } } log.info("Checking bounds dir took", (System.currentTimeMillis() - t1), "ms"); return true; } public void end() { long t1 = System.currentTimeMillis(); log.info("Starting with location hook"); boundaryGrid = new BoundaryGrid(boundaryDirName, saver.getBoundingBox(), props); processLocationRelevantElements(); boundaryGrid = null; long dt = (System.currentTimeMillis() - t1); log.info("======= LocationHook Stats ====="); log.info("QuadTree searches :", cntQTSearch); log.info("unsuccesfull :", cntNotFnd); log.info("unsuccesfull for ways:", cntwayNotFnd); log.info("Location hook finished in", dt, "ms"); } /** * Iterate over all elements for which the boundary assignment should be performed. */ private void processLocationRelevantElements() { // process all nodes that might be converted to a garmin node (tagcount > 0) for (Node node : saver.getNodes().values()) { if (node.getTagCount() > 0) { if (saver.getBoundingBox().contains(node.getLocation())){ processElem(node); if (resultLog.isDebugEnabled()) resultLog.debug("N", node.getId(), locationTagsToString(node)); } } } // process all ways that might be converted to a garmin way (tagcount > 0) for (Way way : saver.getWays().values()) { if (way.getTagCount() > 0) { processElem(way); if (resultLog.isDebugEnabled()) resultLog.debug("W", way.getId(), locationTagsToString(way)); } } // process all multipolygons - the add-pois-to-area function uses its // center point and its tags so the mp must be tagged itself with the bounds // tags for (Relation r : saver.getRelations().values()) { if (r instanceof MultiPolygonRelation) { // check if the mp could be processed Coord mpCenter = ((MultiPolygonRelation) r).getCofG(); if (mpCenter != null && saver.getBoundingBox().contains(mpCenter)){ // create a fake node for which the bounds information is collected Node mpNode = new Node(r.getOriginalId(), mpCenter); mpNode.setFakeId(); processElem(mpNode); // copy the bounds tags back to the multipolygon for (String boundsTag : BoundaryQuadTree.mkgmapTagsArray) { String tagValue = mpNode.getTag(boundsTag); if (tagValue != null) { r.addTag(boundsTag, tagValue); } } if (resultLog.isDebugEnabled()) resultLog.debug("R", r.getId(), locationTagsToString(r)); } } } } /** * Extract the location info and perform a test * against the BoundaryGrid. If found, assign the tags. * @param elem A way or Node */ private void processElem(Element elem){ Tags tags = null; if (elem instanceof Node){ Node node = (Node) elem; tags = search(node.getLocation()); } else if (elem instanceof Way){ Way way = (Way) elem; // try the mid point of the way first int middle = way.getPoints().size() / 2; tags = search(way.getPoints().get(middle)); if (tags == null){ // try 1st point next tags = search(way.getPoints().get(0)); } if (tags == null){ // try last point next tags = search(way.getPoints().get(way.getPoints().size()-1)); } if (tags == null){ // still not found, try rest for (int i = 1; i < way.getPoints().size()-1; i++){ if (i == middle) continue; tags = search(way.getPoints().get(i)); if (tags != null) break; } } if (tags == null) ++cntwayNotFnd; } if (tags == null){ ++cntNotFnd; } else{ // tag the element with all tags referenced by the boundary Iterator<Entry<Short,String>> tagIter = tags.entryShortIterator(); while (tagIter.hasNext()) { Entry<Short,String> tag = tagIter.next(); if (elem.getTag(tag.getKey()) == null){ elem.addTag(tag.getKey(),tag.getValue()); } } } } /** * perform search in grid and maintain statistic counter * @param co a point that is to be searched * @return location relevant tags or null */ private Tags search(Coord co){ if (saver.getBoundingBox().contains(co)){ ++cntQTSearch; return boundaryGrid.get(co); } else return null; } /** * Debugging: * Create a string with location relevant tags ordered by admin_level. * Can be used to compare results with tools like diff. * @param elem the element * @return A new String object */ private String locationTagsToString(Element elem){ StringBuilder res = new StringBuilder(); for (int i = BoundaryQuadTree.mkgmapTagsArray.length-1; i >= 0; --i){ String tagVal = elem.getTag(BoundaryQuadTree.mkgmapTagsArray[i] ); if (tagVal != null) res.append(tagVal); res.append(";"); } return res.toString(); } }