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