/* 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.module.osm; import java.util.ArrayList; import java.util.List; import org.opentripplanner.common.model.P2; import org.opentripplanner.openstreetmap.model.OSMWithTags; /** * Specifies a class of OSM tagged entities (e.g. ways) by a list of tags and their values (which may be wildcards). * The OSMSpecifier which matches the most tags on an OSM entity will win. In the event that several OSMSpecifiers * match the same number of tags, the one that does so using less wildcards will win. For example, if one OSMSpecifier * has the tags (highway=residential, cycleway=*) and another has (highway=residential, surface=paved) and a way has the * tags (highway=residential, cycleway=lane, surface=paved) the second OSMSpecifier will be applied to that way * (2 exact matches beats 1 exact match and a wildcard match). */ public class OSMSpecifier { public List<P2<String>> kvpairs; public OSMSpecifier() { kvpairs = new ArrayList<P2<String>>(); // TODO string-pairs with a proper OSM tag class } public OSMSpecifier(String spec) { this(); setKvpairs(spec); } public void setKvpairs(String spec) { String[] pairs = spec.split(";"); for (String pair : pairs) { String[] kv = pair.split("="); kvpairs.add(new P2<String>(kv[0], kv[1])); } } /** * Calculates a pair of scores expressing how well an OSM entity's tags match this specifier. * * Tags in this specifier are matched against those for the left and right side of the OSM way separately. See: * http://wiki.openstreetmap.org/wiki/Forward_%26_backward,_left_%26_right * TODO: we should probably support forward/backward as well. * TODO: simply count the number of full, partial, and wildcard matches instead of using a scoring system. * * @param match an OSM tagged object to compare to this specifier */ public P2<Integer> matchScores(OSMWithTags match) { int leftScore = 0, rightScore = 0; int leftMatches = 0, rightMatches = 0; for (P2<String> pair : kvpairs) { // TODO why are we repeatedly converting these to lower case every time they are used? // Probably because it used to be possible to set them from Spring XML. String tag = pair.first.toLowerCase(); String value = pair.second.toLowerCase(); String leftMatchValue = match.getTag(tag + ":left"); String rightMatchValue = match.getTag(tag + ":right"); String matchValue = match.getTag(tag); if (leftMatchValue == null) { leftMatchValue = matchValue; } if (rightMatchValue == null) { rightMatchValue = matchValue; } int leftTagScore = getTagScore(value, leftMatchValue); leftScore += leftTagScore; if (leftTagScore > 0) { leftMatches ++; } int rightTagScore = getTagScore(value, rightMatchValue); rightScore += rightTagScore; if (rightTagScore > 0) { rightMatches ++; } } int allMatchLeftBonus = (leftMatches == kvpairs.size()) ? 10 : 0; leftScore += allMatchLeftBonus; int allMatchRightBonus = (rightMatches == kvpairs.size()) ? 10 : 0; rightScore += allMatchRightBonus; P2<Integer> score = new P2<Integer>(leftScore, rightScore); return score; } /** * Calculates a score expressing how well an OSM entity's tags match this specifier. * This does exactly the same thing as matchScores but without regard for :left and :right. */ public int matchScore(OSMWithTags match) { int score = 0; int matches = 0; for (P2<String> pair : kvpairs) { String tag = pair.first.toLowerCase(); String value = pair.second.toLowerCase(); String matchValue = match.getTag(tag); int tagScore = getTagScore(value, matchValue); score += tagScore; if (tagScore > 0) { matches += 1; } } score += matches == kvpairs.size() ? 10 : 0; return score; } /** * Calculates a score indicating how well an OSM tag value matches the given matchValue. * An exact match is worth 100 points, a partial match on the part of the value before a colon is worth 75 points, * and a wildcard match is worth only one point, to serve as a tiebreaker. A score of 0 means they do not match. */ private int getTagScore(String value, String matchValue) { // either this matches on a wildcard, or it matches exactly if (value.equals("*") && matchValue != null) { return 1; // wildcard matches are basically tiebreakers } else if (value.equals(matchValue)) { return 100; } else { if (value.contains(":")) { // treat cases like cobblestone:flattened as cobblestone if a more-specific match // does not apply value = value.split(":", 2)[0]; if (value.equals(matchValue)) { return 75; } else { return 0; } } else { return 0; } } } public void addTag(String key, String value) { kvpairs.add(new P2<String>(key, value)); } public String toString() { StringBuilder builder = new StringBuilder(); for (P2<String> pair : kvpairs) { builder.append(pair.first); builder.append("="); builder.append(pair.second); builder.append(";"); } builder.deleteCharAt(builder.length() - 1); // remove trailing semicolon return builder.toString(); } }