/*
* 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.Area;
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
implements OsmReadingHooks
{
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
;
@
Override
public boolean init
(ElementSaver saver, EnhancedProperties props,
Style style
) {
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
) {
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.
isEmpty()) {
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;
}
@
Override
public void end
() {
long t1 =
System.
currentTimeMillis();
log.
info("Starting with location hook");
Area nodesBounds = saver.
getDataBoundingBox();
if (nodesBounds
!=
null) {
Area bbox = saver.
getBoundingBox();
// calculate the needed bounding box
Area searchBounds = bbox.
intersect(nodesBounds
);
boundaryGrid =
new BoundaryGrid
(boundaryDirName, searchBounds, 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 && 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.
markAsGeneratedFrom(r
);
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
) {
tags = processWayPoints
(((Way
) elem
).
getPoints());
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());
}
}
}
}
private Tags processWayPoints
(final List<Coord
> points
) {
// try the mid point of the way first
final int n = points.
size();
final int middle = n /
2;
final Coord midPoint
;
if (n ==
2) {
midPoint = points.
get(0).
makeBetweenPoint(points.
get(1),
0.5);
} else {
midPoint = points.
get(middle
);
}
Tags tags = search
(midPoint
);
if (tags ==
null) {
// try 1st point next
tags = search
(points.
get(0));
}
if (tags ==
null) {
// try last point next
tags = search
(points.
get(n-
1));
}
if (tags ==
null) {
// still not found, try rest
for (int i =
1; i
< n -
1; i++
) {
if (i
!= middle
) {
tags = search
(points.
get(i
));
if (tags
!=
null)
break;
}
}
}
return tags
;
}
/**
* 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
);
}
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 static 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();
}
}