package org.opentripplanner.traffic; import io.opentraffic.engine.data.pbf.ExchangeFormat; import io.opentraffic.engine.data.stats.SummaryStatistics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; import java.time.DayOfWeek; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; /** * Represents speeds at particular times of day. */ public class SegmentSpeedSample implements Serializable { private static final Logger LOG = LoggerFactory.getLogger(SegmentSpeedSample.class); private static final double KMH_TO_MS = 1000d / 3600d; /** * the overall average speed on this segment, in centimeters per second, with -32,768 representing 0. * This allows representation of speeds up to 2359 kilometers per hour. */ private final short average; /** * The average speeds by hour of week, with 0 being midnight Monday morning GMT. * Coded as above. */ private final short[] hourBins; /** Get a speed estimate in meters per second for the time specified (in milliseconds since the epoch) */ public double getSpeed (long time) { if (hourBins == null) return decodeSpeed(average); // figure out the hour bin Instant instant = Instant.ofEpochMilli(time); OffsetDateTime dt = instant.atOffset(ZoneOffset.UTC); // 0 (Monday) to 6 (Sunday) after subtraction int day = DayOfWeek.from(dt).getValue() - 1; int hour = dt.getHour(); int hourBin = day * 24 + hour; return decodeSpeed(hourBins[hourBin]); } /** Decode a speed to meters per second from its short representation */ private double decodeSpeed (short speed) { return (((double) speed) - Short.MIN_VALUE) / 100d; } /** Encode a speed stored as meters per second to its short representation. */ private short encodeSpeed (double speed) { if (speed < 0) throw new UnsupportedOperationException("negative speeds do not exist."); if (speed > 65535 / 100d) { LOG.warn("Speed is greater than 2359.26 kilometers per hour, clamping. However, are you certain that there is a road with a speed this fast?"); return Short.MAX_VALUE; } return (short) (speed * 100 - Short.MIN_VALUE); } /** Create a speed sample from an OpenTraffic PBF stats object */ public SegmentSpeedSample(ExchangeFormat.BaselineStats stats) { float avg = stats.getAverageSpeed(); if (Float.isNaN(avg)) { LOG.error("Invalid speed sample: average speed is NaN"); throw new IllegalArgumentException("Overall average speed for a sample is NaN."); } this.average = encodeSpeed(avg * KMH_TO_MS); int count = stats.getHourOfWeekAveragesCount(); if (count == 7 * 24) { hourBins = new short[count]; for (int i = 0; i < count; i++) { float speed = stats.getHourOfWeekAverages(i); if (!Float.isNaN(speed)) hourBins[i] = encodeSpeed(speed * KMH_TO_MS); else hourBins[i] = average; } } else { if (count > 0 ) LOG.error("Expected {} hours in speed sample, found {}", 7 * 24, count); hourBins = null; } } /** Create a speed sample from an OpenTraffic stats object directly */ public SegmentSpeedSample(SummaryStatistics stats) { double avg = stats.getMean(); if (Double.isNaN(avg)) { LOG.error("Invalid speed sample: average speed is NaN"); throw new IllegalArgumentException("Overall average speed for a sample is NaN."); } this.average = encodeSpeed(avg); hourBins = new short[7 * 24]; for (int i = 0; i < 7 * 24; i++) { double speed = stats.getMean(); //TODO make it possible to grab summary by hour if (!Double.isNaN(speed)) hourBins[i] = encodeSpeed(speed); else hourBins[i] = average; } } /** create a speed sample using a function */ public SegmentSpeedSample(double averageSpeed, double[] hourBins) { this.average = encodeSpeed(averageSpeed); this.hourBins = new short[hourBins.length]; for (int i = 0; i < hourBins.length; i++) { this.hourBins[i] = Double.isNaN(hourBins[i]) ? this.average : encodeSpeed(hourBins[i]); } } }