/* This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package org.opentripplanner.graph_builder.module.osm;
import org.opentripplanner.common.model.P2;
import org.opentripplanner.graph_builder.annotation.ConflictingBikeTags;
import org.opentripplanner.openstreetmap.model.OSMWay;
import org.opentripplanner.openstreetmap.model.OSMWithTags;
import org.opentripplanner.routing.edgetype.StreetEdge;
import org.opentripplanner.routing.edgetype.StreetTraversalPermission;
import org.opentripplanner.routing.graph.Graph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*/
public class OSMFilter {
private static Logger LOG = LoggerFactory.getLogger(OSMFilter.class);
/**
* Determine whether any mode can or should ever traverse the given way. If not, we leave the
* way out of the OTP graph. Potentially routable ways are those that have the tags : highway=*
* public_transport=platform railway=platform
*
* But not conveyers, proposed highways/roads or those still under construction, and raceways
* (as well as ways where all access is specifically forbidden to the public).
* http://wiki.openstreetmap.org/wiki/Tag:highway%3Dproposed
*/
public static boolean isWayRoutable(OSMWithTags way) {
if (!isOsmEntityRoutable(way))
return false;
String highway = way.getTag("highway");
if (highway != null
&& (highway.equals("conveyer") || highway.equals("proposed")
|| highway.equals("construction") || highway.equals("raceway") || highway
.equals("unbuilt")))
return false;
if (way.isGeneralAccessDenied()) {
// There are exceptions.
return (way.isMotorcarExplicitlyAllowed() || way.isBicycleExplicitlyAllowed() || way
.isPedestrianExplicitlyAllowed() || way.isMotorVehicleExplicitlyAllowed());
}
return true;
}
/**
* Determines whether this OSM way is considered routable. The majority of routable ways are
* those with a highway= tag (which includes everything from motorways to hiking trails).
* Anything with a public_transport=platform or railway=platform tag is also considered routable
* even if it doesn't have a highway tag. Platforms are however filtered out if they are marked
* usage=tourism. This prevents miniature tourist railways like the one in Portland's Zoo from
* receiving a better score and pulling search endpoints away from real transit stops.
*/
public static boolean isOsmEntityRoutable(OSMWithTags osmEntity) {
if (osmEntity.hasTag("highway"))
return true;
if (osmEntity.isTag("public_transport", "platform")
|| osmEntity.isTag("railway", "platform")) {
return !("tourism".equals(osmEntity.getTag("usage")));
}
return false;
}
public static StreetTraversalPermission getPermissionsForEntity(OSMWithTags entity,
StreetTraversalPermission def) {
StreetTraversalPermission permission = null;
/*
* Only a few tags are examined here, because we only care about modes supported by OTP
* (wheelchairs are not of concern here)
*
* Only a few values are checked for, all other values are presumed to be permissive (=>
* This may not be perfect, but is closer to reality, since most people don't follow the
* rules perfectly ;-)
*/
if (entity.isGeneralAccessDenied()) {
// this can actually be overridden
permission = StreetTraversalPermission.NONE;
if (entity.isMotorcarExplicitlyAllowed() || entity.isMotorVehicleExplicitlyAllowed()) {
permission = permission.add(StreetTraversalPermission.CAR);
}
if (entity.isBicycleExplicitlyAllowed()) {
permission = permission.add(StreetTraversalPermission.BICYCLE);
}
if (entity.isPedestrianExplicitlyAllowed()) {
permission = permission.add(StreetTraversalPermission.PEDESTRIAN);
}
} else {
permission = def;
}
if (entity.isMotorcarExplicitlyDenied() || entity.isMotorVehicleExplicitlyDenied()) {
permission = permission.remove(StreetTraversalPermission.CAR);
} else if (entity.isMotorcarExplicitlyAllowed() || entity.isMotorVehicleExplicitlyAllowed()) {
permission = permission.add(StreetTraversalPermission.CAR);
}
if (entity.isBicycleExplicitlyDenied()) {
permission = permission.remove(StreetTraversalPermission.BICYCLE);
} else if (entity.isBicycleExplicitlyAllowed()) {
permission = permission.add(StreetTraversalPermission.BICYCLE);
}
if (entity.isPedestrianExplicitlyDenied()) {
permission = permission.remove(StreetTraversalPermission.PEDESTRIAN);
} else if (entity.isPedestrianExplicitlyAllowed()) {
permission = permission.add(StreetTraversalPermission.PEDESTRIAN);
}
if (entity.isUnderConstruction()) {
permission = StreetTraversalPermission.NONE;
}
if (permission == null)
return def;
return permission;
}
/**
* Computes permissions for an OSMWay.
*
* @param way
* @param def
* @return
*/
public static StreetTraversalPermission getPermissionsForWay(OSMWay way,
StreetTraversalPermission def, Graph graph, boolean banDiscouragedWalking, boolean banDiscouragedBiking) {
StreetTraversalPermission permissions = getPermissionsForEntity(way, def);
/*
* pedestrian rules: everything is two-way (assuming pedestrians are allowed at all) bicycle
* rules: default: permissions;
*
* cycleway=dismount means walk your bike -- the engine will automatically try walking bikes
* any time it is forbidden to ride them, so the only thing to do here is to remove bike
* permissions
*
* oneway=... sets permissions for cars and bikes oneway:bicycle overwrites these
* permissions for bikes only
*
* now, cycleway=opposite_lane, opposite, opposite_track can allow once oneway has been set
* by oneway:bicycle, but should give a warning if it conflicts with oneway:bicycle
*
* bicycle:backward=yes works like oneway:bicycle=no bicycle:backwards=no works like
* oneway:bicycle=yes
*/
// Compute pedestrian permissions.
if (way.isPedestrianExplicitlyAllowed()) {
permissions = permissions.add(StreetTraversalPermission.PEDESTRIAN);
} else if (way.isPedestrianExplicitlyDenied()) {
permissions = permissions.remove(StreetTraversalPermission.PEDESTRIAN);
}
// Check for foot=discouraged, if applicable
if(banDiscouragedWalking && way.hasTag("foot") && way.getTag("foot").equals("discouraged")) {
permissions = permissions.remove(StreetTraversalPermission.PEDESTRIAN);
}
// Compute bike permissions, check consistency.
boolean forceBikes = false;
if (way.isBicycleExplicitlyAllowed()) {
permissions = permissions.add(StreetTraversalPermission.BICYCLE);
forceBikes = true;
}
if (way.isBicycleDismountForced() ||
(banDiscouragedBiking && way.hasTag("bicycle") && way.getTag("bicycle").equals("discouraged"))) {
permissions = permissions.remove(StreetTraversalPermission.BICYCLE);
if (forceBikes) {
LOG.warn(graph.addBuilderAnnotation(new ConflictingBikeTags(way.getId())));
}
}
return permissions;
}
public static StreetTraversalPermission getPermissionsForWay(OSMWay way,
StreetTraversalPermission def, Graph graph) {
return getPermissionsForWay(way, def, graph, false, false);
}
/**
* Check OSM tags for various one-way and one-way-by-mode tags and return a pair of permissions
* for travel along and against the way.
*/
public static P2<StreetTraversalPermission> getPermissions(
StreetTraversalPermission permissions, OSMWay way) {
StreetTraversalPermission permissionsFront = permissions;
StreetTraversalPermission permissionsBack = permissions;
// Check driving direction restrictions.
if (way.isOneWayForwardDriving() || way.isRoundabout()) {
permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE_AND_CAR);
}
if (way.isOneWayReverseDriving()) {
permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE_AND_CAR);
}
// Check bike direction restrictions.
if (way.isOneWayForwardBicycle()) {
permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE);
}
if (way.isOneWayReverseBicycle()) {
permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE);
}
// TODO(flamholz): figure out what this is for.
String oneWayBicycle = way.getTag("oneway:bicycle");
if (OSMWithTags.isFalse(oneWayBicycle) || way.isTagTrue("bicycle:backwards")) {
if (permissions.allows(StreetTraversalPermission.BICYCLE)) {
permissionsFront = permissionsFront.add(StreetTraversalPermission.BICYCLE);
permissionsBack = permissionsBack.add(StreetTraversalPermission.BICYCLE);
}
}
//This needs to be after adding permissions for oneway:bicycle=no
//removes bicycle permission when bicycles need to use sidepath
//TAG: bicycle:forward=use_sidepath
if (way.isForwardDirectionSidepath()) {
permissionsFront = permissionsFront.remove(StreetTraversalPermission.BICYCLE);
}
//TAG bicycle:backward=use_sidepath
if (way.isReverseDirectionSidepath()) {
permissionsBack = permissionsBack.remove(StreetTraversalPermission.BICYCLE);
}
if (way.isOpposableCycleway()) {
permissionsBack = permissionsBack.add(StreetTraversalPermission.BICYCLE);
}
return new P2<StreetTraversalPermission>(permissionsFront, permissionsBack);
}
public static int getStreetClasses(OSMWithTags way) {
int link = 0;
String highway = way.getTag("highway");
if (highway != null && highway.endsWith(("_link"))) {
link = StreetEdge.CLASS_LINK;
}
return getPlatformClass(way) | link;
}
public static int getPlatformClass(OSMWithTags way) {
String highway = way.getTag("highway");
if ("platform".equals(way.getTag("railway"))) {
return StreetEdge.CLASS_TRAIN_PLATFORM;
}
if ("platform".equals(highway) || "platform".equals(way.getTag("public_transport"))) {
if (way.isTagTrue("train") || way.isTagTrue("subway") || way.isTagTrue("tram")
|| way.isTagTrue("monorail")) {
return StreetEdge.CLASS_TRAIN_PLATFORM;
}
return StreetEdge.CLASS_OTHER_PLATFORM;
}
return 0;
}
}