/* 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 (props, 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.impl.osm; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.opentripplanner.common.model.P2; import org.opentripplanner.openstreetmap.model.OSMWithTags; import org.opentripplanner.routing.edgetype.StreetTraversalPermission; import org.opentripplanner.routing.patch.Alert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import lombok.Data; @Data public class WayPropertySet { private static Logger LOG = LoggerFactory.getLogger(WayPropertySet.class); private List<WayPropertyPicker> wayProperties; private List<CreativeNamerPicker> creativeNamers; private List<SlopeOverridePicker> slopeOverrides; /** * SpeedPickers for automobile speeds. */ private List<SpeedPicker> speedPickers; /** * The speed for street segments that match no speedPicker. */ private Float defaultSpeed; private List<NotePicker> notes; private Pattern maxSpeedPattern; public WayProperties defaultProperties; private WayPropertySetSource base; public WayPropertySet() { /* sensible defaults */ defaultProperties = new WayProperties(); defaultProperties.setSafetyFeatures(new P2<Double>(1.0, 1.0)); defaultProperties.setPermission(StreetTraversalPermission.ALL); defaultSpeed = 11.2f; // 11.2 m/s, ~25 mph, standard speed limit in the US wayProperties = new ArrayList<WayPropertyPicker>(); creativeNamers = new ArrayList<CreativeNamerPicker>(); slopeOverrides = new ArrayList<SlopeOverridePicker>(); speedPickers = new ArrayList<SpeedPicker>(); notes = new ArrayList<NotePicker>(); // regex courtesy http://wiki.openstreetmap.org/wiki/Key:maxspeed // and edited maxSpeedPattern = Pattern.compile("^([0-9][\\.0-9]+?)(?:[ ]?(kmh|km/h|kmph|kph|mph|knots))?$"); } public void setBase(WayPropertySetSource base) { this.base = base; WayPropertySet props = base.getWayPropertySet(); creativeNamers = props.getCreativeNamers(); defaultProperties = props.defaultProperties; notes = props.notes; slopeOverrides = props.slopeOverrides; wayProperties = props.wayProperties; } public WayProperties getDataForWay(OSMWithTags way) { WayProperties leftResult = defaultProperties; WayProperties rightResult = defaultProperties; int bestLeftScore = 0; int bestRightScore = 0; List<WayProperties> leftMixins = new ArrayList<WayProperties>(); List<WayProperties> rightMixins = new ArrayList<WayProperties>(); for (WayPropertyPicker picker : getWayProperties()) { OSMSpecifier specifier = picker.getSpecifier(); WayProperties wayProperties = picker.getProperties(); P2<Integer> score = specifier.matchScores(way); int leftScore = score.getFirst(); int rightScore = score.getSecond(); if (picker.isSafetyMixin()) { if (leftScore > 0) { leftMixins.add(wayProperties); } if (rightScore > 0) { rightMixins.add(wayProperties); } } else { if (leftScore > bestLeftScore) { leftResult = wayProperties; bestLeftScore = leftScore; } if (rightScore > bestRightScore) { rightResult = wayProperties; bestRightScore = rightScore; } } } WayProperties result = rightResult.clone(); result.setSafetyFeatures(new P2<Double>(rightResult.getSafetyFeatures().getFirst(), leftResult.getSafetyFeatures().getSecond())); /* apply mixins */ if (leftMixins.size() > 0) { applyMixins(result, leftMixins, false); } if (rightMixins.size() > 0) { applyMixins(result, rightMixins, true); } if ((bestLeftScore == 0 || bestRightScore == 0) && (leftMixins.size() == 0 || rightMixins.size() == 0)) { String all_tags = dumpTags(way); LOG.debug("Used default permissions: " + all_tags); } return result; } private String dumpTags(OSMWithTags way) { /* generate warning message */ String all_tags = null; Map<String, String> tags = way.getTags(); for (Entry<String, String> entry : tags.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); String tag = key + "=" + value; if (all_tags == null) { all_tags = tag; } else { all_tags += "; " + tag; } } return all_tags; } private void applyMixins(WayProperties result, List<WayProperties> mixins, boolean right) { P2<Double> safetyFeatures = result.getSafetyFeatures(); double first = safetyFeatures.getFirst(); double second = safetyFeatures.getSecond(); for (WayProperties properties : mixins) { if (right) { second *= properties.getSafetyFeatures().getSecond(); } else { first *= properties.getSafetyFeatures().getFirst(); } } result.setSafetyFeatures(new P2<Double>(first, second)); } public String getCreativeNameForWay(OSMWithTags way) { CreativeNamer bestNamer = null; int bestScore = 0; for (CreativeNamerPicker picker : creativeNamers) { OSMSpecifier specifier = picker.getSpecifier(); CreativeNamer namer = picker.getNamer(); int score = specifier.matchScore(way); if (score > bestScore) { bestNamer = namer; bestScore = score; } } if (bestNamer == null) { return null; } return bestNamer.generateCreativeName(way); } /** * Calculate the automobile speed, in meters per second, for this way. */ public float getCarSpeedForWay(OSMWithTags way, boolean back) { // first, check for maxspeed tags Float speed = null; Float currentSpeed; if (way.hasTag("maxspeed:motorcar")) speed = getMetersSecondFromSpeed(way.getTag("maxspeed:motorcar")); if (speed == null && !back && way.hasTag("maxspeed:forward")) speed = getMetersSecondFromSpeed(way.getTag("maxspeed:forward")); if (speed == null && back && way.hasTag("maxspeed:reverse")) speed = getMetersSecondFromSpeed(way.getTag("maxspeed:reverse")); if (speed == null && way.hasTag("maxspeed:lanes")) { for (String lane : way.getTag("maxspeed:lanes").split("\\|")) { currentSpeed = getMetersSecondFromSpeed(lane); // Pick the largest speed from the tag // currentSpeed might be null if it was invalid, for instance 10|fast|20 if (currentSpeed != null && (speed == null || currentSpeed > speed)) speed = currentSpeed; } } if (way.hasTag("maxspeed") && speed == null) speed = getMetersSecondFromSpeed(way.getTag("maxspeed")); // this would be bad, as the segment could never be traversed by an automobile // The small epsilon is to account for possible rounding errors if (speed != null && speed < 0.0001) LOG.warn("Zero or negative automobile speed detected at {} based on OSM " + "maxspeed tags; ignoring these tags", this); // if there was a defined speed and it's not 0, we're done if (speed != null) return speed; // otherwise, we use the speedPickers int bestScore = 0; Float bestSpeed = null; int score; // SpeedPickers are constructed in DefaultWayPropertySetSource with an OSM specifier // (e.g. highway=motorway) and a default speed for that segment. for (SpeedPicker picker : speedPickers) { OSMSpecifier specifier = picker.getSpecifier(); score = specifier.matchScore(way); if (score > bestScore) { bestScore = score; bestSpeed = picker.getSpeed(); } } if (bestSpeed != null) return bestSpeed; else return this.defaultSpeed; } public Set<Alert> getNoteForWay(OSMWithTags way) { HashSet<Alert> out = new HashSet<Alert>(); for (NotePicker picker : notes) { OSMSpecifier specifier = picker.getSpecifier(); NoteProperties noteProperties = picker.getNoteProperties(); if (specifier.matchScore(way) > 0) { out.add(Alert.createSimpleAlerts(noteProperties.generateNote(way).intern())); } } if (out.size() == 0) { return null; } return out; } public boolean getSlopeOverride(OSMWithTags way) { boolean result = false; int bestScore = 0; for (SlopeOverridePicker picker : slopeOverrides) { OSMSpecifier specifier = picker.getSpecifier(); int score = specifier.matchScore(way); if (score > bestScore) { result = picker.getOverride(); bestScore = score; } } return result; } public void addProperties(OSMSpecifier spec, WayProperties properties, boolean mixin) { getWayProperties().add(new WayPropertyPicker(spec, properties, mixin)); } public void addProperties(OSMSpecifier spec, WayProperties properties) { getWayProperties().add(new WayPropertyPicker(spec, properties, false)); } public void addCreativeNamer(OSMSpecifier spec, CreativeNamer namer) { getCreativeNamers().add(new CreativeNamerPicker(spec, namer)); } public void addNote(OSMSpecifier osmSpecifier, NoteProperties properties) { notes.add(new NotePicker(osmSpecifier, properties)); } public void setSlopeOverride(OSMSpecifier spec, boolean override) { slopeOverrides.add(new SlopeOverridePicker(spec, override)); } public boolean equals(Object o) { if (o instanceof WayPropertySet) { WayPropertySet other = (WayPropertySet) o; return (defaultProperties.equals(other.defaultProperties) && wayProperties.equals(other.wayProperties) && creativeNamers.equals(other.creativeNamers) && slopeOverrides.equals(other.slopeOverrides) && notes.equals(other.notes)); } return false; } public int hashCode() { return defaultProperties.hashCode() + wayProperties.hashCode() + creativeNamers.hashCode() + slopeOverrides.hashCode(); } public void addSpeedPicker(SpeedPicker picker) { this.speedPickers.add(picker); } public Float getMetersSecondFromSpeed(String speed) { Matcher m = maxSpeedPattern.matcher(speed); if (!m.matches()) return null; float originalUnits; try { originalUnits = (float) Double.parseDouble(m.group(1)); } catch (NumberFormatException e) { LOG.warn("Could not parse max speed {}", m.group(1)); return null; } String units = m.group(2); if (units == null || units.equals("")) units = "kmh"; // we'll be doing quite a few string comparisons here units = units.intern(); float metersSecond; if (units == "kmh" || units == "km/h" || units == "kmph" || units == "kph") metersSecond = 0.277778f * originalUnits; else if (units == "mph") metersSecond = 0.446944f * originalUnits; else if (units == "knots") metersSecond = 0.514444f * originalUnits; else return null; return metersSecond; } }