All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.osm.wayproperty.specifier.BestMatchSpecifier Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.osm.wayproperty.specifier;

import java.util.Arrays;
import java.util.stream.Collectors;
import org.opentripplanner.osm.model.OsmEntity;
import org.opentripplanner.utils.tostring.ToStringBuilder;

/**
 * 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 fewer
 * 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 specifier will be applied to that
 * way (2 exact matches beats 1 exact match and a wildcard match).
 */
public class BestMatchSpecifier implements OsmSpecifier {

  /**
   * If a tag matches completely (both key and value match) this is the score returned for it.
   *
   * @see ExactMatchSpecifier#MATCH_MULTIPLIER
   */
  public static final int EXACT_MATCH_SCORE = 100;
  public static final int WILDCARD_MATCH_SCORE = 1;
  public static final int NO_MATCH_SCORE = 0;
  private final Condition[] conditions;

  /**
   * @deprecated Logic is fuzzy and unpredictable, use ExactMatchSpecifier instead
   */
  @Deprecated
  public BestMatchSpecifier(String spec) {
    conditions = OsmSpecifier.parseConditions(spec, ";");
  }

  @Override
  public Scores matchScores(OsmEntity way) {
    int backwardScore = 0, forwardScore = 0;
    int backwardMatches = 0, forwardMatches = 0;

    for (var test : conditions) {
      var forwardMatch = test.matchForward(way);
      var backwardMatch = test.matchBackward(way);

      int backwardTagScore = toTagScore(backwardMatch);
      backwardScore += backwardTagScore;
      if (backwardTagScore > 0) {
        backwardMatches++;
      }
      int forwardTagScore = toTagScore(forwardMatch);
      forwardScore += forwardTagScore;
      if (forwardTagScore > 0) {
        forwardMatches++;
      }
    }

    int allMatchBackwardBonus = (backwardMatches == conditions.length) ? 10 : 0;
    backwardScore += allMatchBackwardBonus;
    int allMatchForwardBonus = (forwardMatches == conditions.length) ? 10 : 0;
    forwardScore += allMatchForwardBonus;
    return new Scores(forwardScore, backwardScore);
  }

  @Override
  public int matchScore(OsmEntity way) {
    int score = 0;
    int matches = 0;
    for (var test : conditions) {
      var matchValue = test.match(way);
      int tagScore = toTagScore(matchValue);
      score += tagScore;
      if (tagScore > 0) {
        matches += 1;
      }
    }
    score += matches == conditions.length ? 10 : 0;
    return score;
  }

  @Override
  public String toDocString() {
    return Arrays.stream(conditions).map(Object::toString).collect(Collectors.joining("; "));
  }

  @Override
  public String toString() {
    return ToStringBuilder.of(this.getClass()).addObj("conditions", conditions).toString();
  }

  /**
   * 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 static int toTagScore(Condition.MatchResult res) {
    return switch (res) {
      case EXACT -> EXACT_MATCH_SCORE;
      // wildcard matches are basically tiebreakers
      case WILDCARD -> WILDCARD_MATCH_SCORE;
      // no match means no score
      case NONE -> NO_MATCH_SCORE;
    };
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy