/*
* Copyright (C) 2010 - 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.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.Exit;
import uk.me.parabola.log.Logger;
import uk.me.parabola.util.ElementQuadTree;
import uk.me.parabola.util.EnhancedProperties;
/**
* Operations mostly on highways that have to be performed during reading
* the OSM input file.
*
* Some of this would be much better done in a style file or by extending the style system.
*/
public class HighwayHooks
implements OsmReadingHooks
{
private static final Logger log =
Logger.
getLogger(HighwayHooks.
class);
private final List<Element> motorways =
new ArrayList<>(); // will all be Ways
private final List<Element> exits =
new ArrayList<>();
private boolean makeOppositeCycleways
;
private ElementSaver saver
;
private boolean linkPOIsToWays
;
private Node currentNodeInWay
;
@
Override
public boolean init
(ElementSaver saver, EnhancedProperties props,
Style style
) {
this.
saver = saver
;
if(props.
getProperty("make-all-cycleways",
false)) {
log.
error("option make-all-cycleways is deprecated, please use make-opposite-cycleways");
makeOppositeCycleways =
true;
}
else {
makeOppositeCycleways = props.
getProperty("make-opposite-cycleways",
false);
}
linkPOIsToWays = props.
getProperty("link-pois-to-ways",
false);
currentNodeInWay =
null;
return true;
}
@
Override
public Set<String> getUsedTags
() {
Set<String> usedTags =
new HashSet<>(Arrays.
asList("highway",
"access",
"barrier",
"oneway",
"junction",
"name",
Exit.
TAG_ROAD_REF,
"ref",
"motorroad"));
if (makeOppositeCycleways
) {
// need the additional tags
usedTags.
add("cycleway");
usedTags.
add("bicycle");
usedTags.
add("oneway:bicycle");
usedTags.
add("bicycle:oneway");
usedTags.
add("cycleway:left");
usedTags.
add("cycleway:right");
}
return usedTags
;
}
@
Override
public void onAddNode
(Node node
) {
String highway = node.
getTag("highway");
if (highway
!=
null && ("motorway_junction".
equals(highway
) ||
"services".
equals(highway
) ||
"rest_area".
equals(highway
))) {
exits.
add(node
);
node.
addTag("mkgmap:osmid",
String.
valueOf(node.
getId()));
}
}
@
Override
public void onNodeAddedToWay
(Way way,
long id
) {
if (!linkPOIsToWays
)
return;
currentNodeInWay = saver.
getNode(id
);
if (currentNodeInWay ==
null)
return;
Coord co = currentNodeInWay.
getLocation();
// if this Coord is also a POI, replace it with an
// equivalent CoordPOI that contains a reference to
// the POI's Node so we can access the POI's tags
if (!(co
instanceof CoordPOI
)) {
// for now, only do this for nodes that have
// certain tags otherwise we will end up creating
// a CoordPOI for every node in the way
final String[] coordPOITags =
{ "barrier",
"highway" };
for (String cpt : coordPOITags
) {
if (currentNodeInWay.
getTag(cpt
) !=
null) {
// the POI has one of the approved tags so
// replace the Coord with a CoordPOI
CoordPOI cp =
new CoordPOI
(co
);
saver.
addPoint(id, cp
);
// we also have to jump through hoops to
// make a new version of Node because we
// can't replace the Coord that defines
// its location
Node newNode =
new Node(id, cp
);
newNode.
copyTags(currentNodeInWay
);
saver.
addNode(newNode
);
// tell the CoordPOI what node it's
// associated with
cp.
setNode(newNode
);
co = cp
;
// if original node is in exits, replace it
if (exits.
remove(currentNodeInWay
))
exits.
add(newNode
);
currentNodeInWay = newNode
;
break;
}
}
}
if (co
instanceof CoordPOI
) {
// flag this Way as having a CoordPOI so it
// will be processed later
way.
addTag("mkgmap:way-has-pois",
"true");
if (log.
isInfoEnabled())
log.
info("link-pois-to-ways: Linking POI", currentNodeInWay.
toBrowseURL(),
"to way at", co.
toOSMURL());
}
}
@
Override
public void onAddWay
(Way way
) {
String highway = way.
getTag("highway");
if (highway
!=
null) {
// if the way is a roundabout but isn't already
// flagged as "oneway", flag it here
if ("roundabout".
equals(way.
getTag("junction")) && way.
getTag("oneway") ==
null) {
way.
addTag("oneway",
"yes");
}
if (makeOppositeCycleways
&& !"cycleway".
equals(highway
)){
String onewayTag = way.
getTag("oneway");
if (way.
tagIsLikeYes("oneway") ||
"-1".
equals(onewayTag
)){
String cycleway = way.
getTag("cycleway");
// we have a oneway street, check if it allows bicycles to travel in opposite direction
if ("no".
equals(way.
getTag("oneway:bicycle"))
||
"no".
equals(way.
getTag("bicycle:oneway"))
||
"opposite".
equals(cycleway
) ||
"opposite_lane".
equals(cycleway
)
||
"opposite_track".
equals(cycleway
)
||
"opposite_lane".
equals(way.
getTag("cycleway:left"))
||
"opposite_lane".
equals(way.
getTag("cycleway:right"))
||
"opposite_track".
equals(way.
getTag("cycleway:left"))
||
"opposite_track".
equals(way.
getTag("cycleway:right"))) {
way.
addTag("mkgmap:make-cycle-way",
"yes");
}
}
}
}
if ("motorway".
equals(highway
) ||
"trunk".
equals(highway
) ||
"primary".
equals(highway
) || way.
tagIsLikeYes("motorroad"))
motorways.
add(way
);
else if (linkPOIsToWays
&& ("services".
equals(highway
) ||
"rest_area".
equals(highway
))) {
exits.
add(way
);
way.
addTag("mkgmap:osmid",
String.
valueOf(way.
getId()));
}
}
@
Override
public void end
() {
finishExits
();
exits.
clear();
motorways.
clear();
}
private static final int XTRA =
150; // very approx 300m
private void finishExits
() {
if (exits.
isEmpty() || motorways.
isEmpty())
return;
ElementQuadTree majorRoads =
new ElementQuadTree
(saver.
getBoundingBox(), motorways
);
for (Element e : exits
) {
String refTag = Exit.
TAG_ROAD_REF;
if (e.
getTag(refTag
) ==
null) {
String exitName = e.
getTag("name");
if (exitName ==
null)
exitName = e.
getTag("ref");
String ref =
null;
Way motorway =
null;
Area bBox
;
if (e
instanceof Node)
bBox =
Area.
getBBox(Collections.
singletonList(((Node) e
).
getLocation()));
else
bBox =
Area.
getBBox(((Way
) e
).
getPoints());
String highway = e.
getTag("highway");
final boolean isServices =
"services".
equals(highway
) ||
"rest_area".
equals(highway
);
if (isServices
) // services will be just off the road, so increase size
bBox =
new Area(bBox.
getMinLat() - XTRA, bBox.
getMinLong() - XTRA, bBox.
getMaxLat() + XTRA, bBox.
getMaxLong() + XTRA
);
List<Way
> possibleRoads =
new ArrayList<>();
for (Element w : majorRoads.
get(bBox
)) {
motorway =
(Way
) w
;
ref = motorway.
getTag("ref");
if (ref
!=
null) {
if (isServices
) {
possibleRoads.
add(motorway
); // save all possibilities
} else { // probably on 2+ roads, save possibilities to find the more major road (doesn't have to be motorway)
if (!(e
instanceof Node))
log.
warn("Motorway exit", exitName,
"expected to be a Node", e
);
else if (motorway.
getPoints().
contains(((Node) e
).
getLocation()))
possibleRoads.
add(motorway
);
}
}
}
if (possibleRoads.
size() > 1) {
if (isServices
) { // pick the closest road
Coord serviceCoord
;
if (e
instanceof Node)
serviceCoord =
((Node) e
).
getLocation();
else
// Simple-minded logic to see if a Way that probably defines [part of] a
// services area, hence might become a POI if option --link-pois-to-ways,
// is near the specified road.
// Just pick an arbitary Coord on the services boundary and find the nearest
// any Coord in the roads.
// No need to check if it is near the line between far-apart Coords because
// there also needs to be a junction so that can get off the road to the
// services.
serviceCoord =
((Way
)e
).
getFirstPoint();
long closestRoad =
Long.
MAX_VALUE;
for (Way road : possibleRoads
) {
long closestCoord =
Long.
MAX_VALUE;
for (Coord pointOnRoad : road.
getPoints()) {
long dist = pointOnRoad.
distanceInHighPrecSquared(serviceCoord
);
if (dist
< closestCoord
)
closestCoord = dist
;
}
if (closestCoord
< closestRoad
) {
closestRoad = closestCoord
;
motorway = road
;
ref = motorway.
getTag("ref");
}
}
} else { // pick the most major road
int bestRoad =
Integer.
MAX_VALUE;
for (Way road : possibleRoads
) {
String roadType = road.
getTag("highway");
int thisRoad =
4;
if ("motorway".
equals(roadType
))
thisRoad =
0;
else if (road.
tagIsLikeYes("motorroad"))
thisRoad =
1;
else if ("trunk".
equals(roadType
))
thisRoad =
2;
else if ("primary".
equals(roadType
))
thisRoad =
3;
if (thisRoad
< bestRoad
) {
bestRoad = thisRoad
;
motorway = road
;
ref = motorway.
getTag("ref");
}
}
}
//log.info("Exit", exit, possibleRoads.size(), "options, chosen:", motorway, ref);
} // else 0 or 1 road; ref/motorway null or set correctly
if (ref
!=
null) {
log.
info("Adding", refTag +
"=" + ref,
"to exit", exitName
);
e.
addTag(refTag, ref
);
} else if(motorway
!=
null) {
log.
warn("Motorway exit", exitName,
"is positioned on a motorway that doesn't have a 'ref' tag", e
);
}
}
}
}
}