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

org.opentripplanner.graph_builder.module.osm.WayPropertySet Maven / Gradle / Ivy

package org.opentripplanner.graph_builder.module.osm;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.opentripplanner.common.model.P2;
import org.opentripplanner.common.model.T2;
import org.opentripplanner.openstreetmap.model.OSMWithTags;
import org.opentripplanner.routing.alertpatch.Alert;
import org.opentripplanner.routing.edgetype.StreetTraversalPermission;
import org.opentripplanner.routing.services.notes.NoteMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.opentripplanner.util.I18NString;

/**
 * Information given to the GraphBuilder about how to assign permissions, safety values, names, etc. to edges based on OSM tags.
 * TODO rename so that the connection with OSM tags is obvious
 *
 * WayPropertyPickers, CreativeNamePickers, SlopeOverridePickers, and SpeedPickers are applied to ways based on how well
 * their OSMSpecifiers match a given OSM way. Generally one OSMSpecifier will win out over all the others based on the
 * number of exact, partial, and wildcard tag matches. See OSMSpecifier for more details on the matching process.
 */
public class WayPropertySet {
    private static Logger LOG = LoggerFactory.getLogger(WayPropertySet.class);

    private List wayProperties;

    /** Assign names to ways that do not have them based on OSM tags. */
    private List creativeNamers;

    private List slopeOverrides;
    
    /** Assign automobile speeds based on OSM tags. */
    private List speedPickers;
    
    /** The automobile speed for street segments that do not match any SpeedPicker. */
    public Float defaultSpeed;

    private List notes;
    
    private Pattern maxSpeedPattern;

    /** The WayProperties applied to all ways that do not match any WayPropertyPicker. */
    public WayProperties defaultProperties;

    public WayPropertySetSource base;

    public WayPropertySet() {
        /* sensible defaults */
        defaultProperties = new WayProperties();
        defaultProperties.setSafetyFeatures(new P2(1.0, 1.0));
        defaultProperties.setPermission(StreetTraversalPermission.ALL);
        defaultSpeed = 11.2f; // 11.2 m/s ~= 25 mph ~= 40 kph, standard speed limit in the US
        wayProperties = new ArrayList();
        creativeNamers = new ArrayList();
        slopeOverrides = new ArrayList();
        speedPickers = new ArrayList();
        notes = new ArrayList();
        // 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))?$");
    }

    /**
     * Applies the WayProperties whose OSMPicker best matches this way. In addition, WayProperties that are mixins
     * will have their safety values applied if they match at all.
     */
    public WayProperties getDataForWay(OSMWithTags way) {
        WayProperties leftResult = defaultProperties;
        WayProperties rightResult = defaultProperties;
        int bestLeftScore = 0;
        int bestRightScore = 0;
        List leftMixins = new ArrayList();
        List rightMixins = new ArrayList();
        for (WayPropertyPicker picker : wayProperties) {
            OSMSpecifier specifier = picker.getSpecifier();
            WayProperties wayProperties = picker.getProperties();
            P2 score = specifier.matchScores(way);
            int leftScore = score.first;
            int rightScore = score.second;
            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(rightResult.getSafetyFeatures().first,
                leftResult.getSafetyFeatures().second));

        /* 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 tags = way.getTags();
        for (Entry 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 mixins, boolean right) {
        P2 safetyFeatures = result.getSafetyFeatures();
        double first = safetyFeatures.first;
        double second = safetyFeatures.second;
        for (WayProperties properties : mixins) {
            if (right) {
                second *= properties.getSafetyFeatures().second;
            } else {
                first *= properties.getSafetyFeatures().first;
            }
        }
        result.setSafetyFeatures(new P2(first, second));
    }

    public I18NString getCreativeNameForWay(OSMWithTags way) {
        CreativeNamer bestNamer = null;
        int bestScore = 0;
        for (CreativeNamerPicker picker : creativeNamers) {
            OSMSpecifier specifier = picker.specifier;
            CreativeNamer namer = picker.namer;
            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.specifier;
            score = specifier.matchScore(way);
            if (score > bestScore) {
                bestScore = score;
                bestSpeed = picker.speed;
            }
        }
        
        if (bestSpeed != null)
            return bestSpeed;
        else
            return this.defaultSpeed;
    }

    public Set> getNoteForWay(OSMWithTags way) {
        HashSet> out = new HashSet<>();
        for (NotePicker picker : notes) {
            OSMSpecifier specifier = picker.specifier;
            NoteProperties noteProperties = picker.noteProperties;
            if (specifier.matchScore(way) > 0) {
                out.add(noteProperties.generateNote(way));
            }
        }
        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) {
        wayProperties.add(new WayPropertyPicker(spec, properties, mixin));
    }

    public void addProperties(OSMSpecifier spec, WayProperties properties) {
        wayProperties.add(new WayPropertyPicker(spec, properties, false));
    }

    public void addCreativeNamer(OSMSpecifier spec, CreativeNamer namer) {
        creativeNamers.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;
    }
    
	public void createNames(String spec, String patternKey) {
		String pattern = patternKey;
		CreativeNamer namer = new CreativeNamer(pattern);
		addCreativeNamer(new OSMSpecifier(spec), namer);
	}

	public  void createNotes(String spec, String patternKey, NoteMatcher matcher) {
		String pattern = patternKey;
		// TODO: notes aren't localized
		NoteProperties properties = new NoteProperties(pattern, matcher);
		addNote(new OSMSpecifier(spec), properties);
	}

	public void setProperties(String spec,
			StreetTraversalPermission permission) {
		setProperties( spec, permission, 1.0, 1.0);
	}

	/**
	 * Note that the safeties here will be adjusted such that the safest street
	 * has a safety value of 1, with all others scaled proportionately.
	 */
	public void setProperties(String spec,
			StreetTraversalPermission permission, double safety, double safetyBack) {
		setProperties(spec, permission, safety, safetyBack, false);
	}

	public void setProperties(String spec,
			StreetTraversalPermission permission, double safety, double safetyBack, boolean mixin) {
		WayProperties properties = new WayProperties();
		properties.setPermission(permission);
		properties.setSafetyFeatures(new P2(safety, safetyBack));
		addProperties(new OSMSpecifier(spec), properties, mixin);
	}

	public void setCarSpeed(String spec, float speed) {
		SpeedPicker picker = new SpeedPicker();
		picker.specifier = new OSMSpecifier(spec);
		picker.speed = speed;
		addSpeedPicker(picker);
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy