Package org.onebusaway.transit_data_federation.impl

Source Code of org.onebusaway.transit_data_federation.impl.StopSequenceCollectionServiceImpl$PatternStats

/**
* Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*         http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onebusaway.transit_data_federation.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.onebusaway.collections.Counter;
import org.onebusaway.collections.FactoryMap;
import org.onebusaway.collections.Max;
import org.onebusaway.collections.tuple.Pair;
import org.onebusaway.collections.tuple.Tuples;
import org.onebusaway.geospatial.services.SphericalGeometryLibrary;
import org.onebusaway.transit_data_federation.model.StopSequence;
import org.onebusaway.transit_data_federation.model.StopSequenceCollection;
import org.onebusaway.transit_data_federation.model.StopSequenceCollectionKey;
import org.onebusaway.transit_data_federation.model.narrative.TripNarrative;
import org.onebusaway.transit_data_federation.services.StopSequenceCollectionService;
import org.onebusaway.transit_data_federation.services.narrative.NarrativeService;
import org.onebusaway.transit_data_federation.services.transit_graph.BlockTripEntry;
import org.onebusaway.transit_data_federation.services.transit_graph.StopEntry;
import org.onebusaway.transit_data_federation.services.transit_graph.TripEntry;
import org.onebusaway.utility.collections.TreeUnionFind;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* Construct a set of {@link StopSequenceCollection} collection for each route.
* A collection contains a set of {@link StopSequence} sequences that are headed
* in the same direction for a particular route, along with a general
* description of the destinations for those stop sequences and general start
* and stop locations for the sequences.
*
* @author bdferris
*/
@Component
public class StopSequenceCollectionServiceImpl implements
    StopSequenceCollectionService {

  private static final double SERVICE_PATTERN_TRIP_COUNT_RATIO_MIN = 0.2;

  private static final double STOP_SEQUENCE_MIN_COMMON_RATIO = 0.3;

  private NarrativeService _narrativeService;

  @Autowired
  public void setNarrativeService(NarrativeService narrativeService) {
    _narrativeService = narrativeService;
  }

  public List<StopSequenceCollection> getStopSequencesAsCollections(
      List<StopSequence> sequences) {

    pruneEmptyStopSequences(sequences);

    if (sequences.isEmpty())
      return new ArrayList<StopSequenceCollection>();

    Map<StopSequence, PatternStats> sequenceStats = getStatsForStopSequences(sequences);
    Map<String, List<StopSequence>> sequenceGroups = getGroupsForStopSequences(sequences);

    return constructCollections(sequenceStats, sequenceGroups);
  }

  /**
   * Remove stop sequences from a list that do not contain any stops
   *
   * @param stopSequences
   */
  private void pruneEmptyStopSequences(List<StopSequence> stopSequences) {
    for (Iterator<StopSequence> it = stopSequences.iterator(); it.hasNext();) {
      StopSequence st = it.next();
      if (st.getStops().isEmpty())
        it.remove();
    }
  }

  /**
   * Computes some general statistics for each {@link StopSequence} in a
   * collection, including the number of trips taking that stop sequence, the
   * set of regions for the destination of the stop sequence
   *
   * @param sequences
   * @return the computed statistics
   */
  private Map<StopSequence, PatternStats> getStatsForStopSequences(
      List<StopSequence> sequences) {

    Map<StopSequence, PatternStats> patternStats = new HashMap<StopSequence, PatternStats>();

    for (StopSequence sequence : sequences) {
      PatternStats stats = new PatternStats();
      stats.tripCounts = sequence.getTripCount();
      stats.segment = getSegmentForStopSequence(sequence);
      patternStats.put(sequence, stats);
    }

    return patternStats;
  }

  /**
   * Compute a {@link Segment} object for the specified {@link StopSequence}. A
   * Segment generally captures the start and end location of the stop sequence,
   * along with the sequence's total length.
   *
   * @param pattern
   * @return
   */
  private Segment getSegmentForStopSequence(StopSequence pattern) {

    Segment segment = new Segment();

    List<StopEntry> stops = pattern.getStops();
    StopEntry prev = null;

    for (StopEntry stop : stops) {
      if (prev == null) {
        segment.fromLat = stop.getStopLat();
        segment.fromLon = stop.getStopLon();
      } else {
        segment.distance += SphericalGeometryLibrary.distance(
            prev.getStopLat(), prev.getStopLon(), stop.getStopLat(),
            stop.getStopLon());
      }
      segment.toLat = stop.getStopLat();
      segment.toLon = stop.getStopLon();
      prev = stop;
    }

    return segment;
  }

  /**
   * Group StopSequences by common direction. If all the stopSequences have a
   * direction id, then we use that to do the grouping. Otherwise...
   *
   * @param sequences
   *
   * @return
   */
  private Map<String, List<StopSequence>> getGroupsForStopSequences(
      List<StopSequence> sequences) {

    boolean allSequencesHaveDirectionId = true;

    for (StopSequence sequence : sequences) {
      if (sequence.getDirectionId() == null)
        allSequencesHaveDirectionId = false;
    }

    if (allSequencesHaveDirectionId) {
      Map<String, List<StopSequence>> result = groupStopSequencesByDirectionIds(sequences);
      if (result.size() > 0)
        return result;
    }

    return groupStopSequencesByNotDirectionIds(sequences);
  }

  /**
   * Group the StopSequences by their direction ids.
   *
   * @param sequences
   * @return
   */
  private Map<String, List<StopSequence>> groupStopSequencesByDirectionIds(
      Iterable<StopSequence> sequences) {

    Map<String, List<StopSequence>> groups = new FactoryMap<String, List<StopSequence>>(
        new ArrayList<StopSequence>());

    for (StopSequence sequence : sequences) {
      String directionId = sequence.getDirectionId();
      groups.get(directionId).add(sequence);
    }

    return groups;
  }

  private Map<String, List<StopSequence>> groupStopSequencesByNotDirectionIds(
      Iterable<StopSequence> sequences) {

    TreeUnionFind<StopSequence> unionFind = new TreeUnionFind<StopSequence>();

    for (StopSequence stopSequenceA : sequences) {

      unionFind.find(stopSequenceA);

      for (StopSequence stopSequenceB : sequences) {
        if (stopSequenceA == stopSequenceB)
          continue;
        double ratio = getMaxCommonStopSequenceRatio(stopSequenceA,
            stopSequenceB);
        if (ratio >= STOP_SEQUENCE_MIN_COMMON_RATIO)
          unionFind.union(stopSequenceA, stopSequenceB);

      }
    }

    Map<String, List<StopSequence>> results = new HashMap<String, List<StopSequence>>();
    int index = 0;

    for (Set<StopSequence> sequencesByDirection : unionFind.getSetMembers()) {
      String key = Integer.toString(index);
      List<StopSequence> asList = new ArrayList<StopSequence>(
          sequencesByDirection);
      results.put(key, asList);
      index++;
    }

    return results;
  }

  /**
   *
   * @param route
   * @param sequenceStats
   * @param sequencesByGroupId
   * @return
   */
  private List<StopSequenceCollection> constructCollections(
      Map<StopSequence, PatternStats> sequenceStats,
      Map<String, List<StopSequence>> sequencesByGroupId) {

    computeContinuations(sequenceStats, sequencesByGroupId);

    Set<String> allNames = new HashSet<String>();
    Map<String, String> directionToName = new HashMap<String, String>();
    Map<String, Segment> segments = new HashMap<String, Segment>();

    for (Map.Entry<String, List<StopSequence>> entry : sequencesByGroupId.entrySet()) {

      String direction = entry.getKey();
      List<StopSequence> sequences = entry.getValue();

      Max<StopSequence> maxTripCount = new Max<StopSequence>();

      Counter<String> names = new Counter<String>();

      for (StopSequence sequence : sequences) {
        maxTripCount.add(sequence.getTripCount(), sequence);

        for (BlockTripEntry blockTrip : sequence.getTrips()) {
          TripEntry trip = blockTrip.getTrip();
          TripNarrative tripNarrative = _narrativeService.getTripForId(trip.getId());
          String headsign = tripNarrative.getTripHeadsign();
          if (headsign != null && headsign.length() > 0)
            names.increment(headsign);
        }
      }

      String dName = names.getMax();

      RecursiveStats rs = new RecursiveStats();
      rs.maxTripCount = (long) maxTripCount.getMaxValue();

      exploreStopSequences(rs, sequenceStats, sequences, "");

      allNames.add(dName);
      directionToName.put(direction, dName);

      segments.put(direction, rs.longestSegment.getMaxElement());
    }

    if (allNames.size() < directionToName.size()) {
      for (Map.Entry<String, String> entry : directionToName.entrySet()) {
        String direction = entry.getKey();
        String name = entry.getValue();
        direction = direction.charAt(0) + direction.substring(1).toLowerCase();
        entry.setValue(name + " - " + direction);
      }
    }

    List<StopSequenceCollection> blocks = new ArrayList<StopSequenceCollection>();

    for (Map.Entry<String, String> entry : directionToName.entrySet()) {

      String direction = entry.getKey();
      String name = entry.getValue();
      List<StopSequence> patterns = sequencesByGroupId.get(direction);

      Segment segment = segments.get(direction);

      // System.out.println("  " + direction + " => " + name);
      StopSequenceCollection block = new StopSequenceCollection();

      if (segment.fromLat == 0.0)
        throw new IllegalStateException("what?");

      StopSequenceCollectionKey key = new StopSequenceCollectionKey(null,
          direction);
      block.setId(key);
      block.setPublicId(direction);
      block.setDescription(name);
      block.setStopSequences(patterns);
      block.setStartLat(segment.fromLat);
      block.setStartLon(segment.fromLon);
      block.setEndLat(segment.toLat);
      block.setEndLon(segment.toLon);

      blocks.add(block);
    }

    return blocks;
  }

  /**
   * For each given StopSequence, we wish to compute the set of StopSequences
   * that continue the given StopSequence. We say one StopSequence continues
   * another if the two stops sequences have the same route and direction id and
   * each trip in the first StopSequence is immediately followed by a Trip from
   * the second StopSequence, as defined by a block id.
   *
   * @param sequenceStats
   * @param sequencesByGroupId
   */
  private void computeContinuations(
      Map<StopSequence, PatternStats> sequenceStats,
      Map<String, List<StopSequence>> sequencesByGroupId) {

    Map<BlockTripEntry, StopSequence> stopSequencesByTrip = new HashMap<BlockTripEntry, StopSequence>();

    Map<StopSequence, String> stopSequenceGroupIds = new HashMap<StopSequence, String>();
    for (Map.Entry<String, List<StopSequence>> entry : sequencesByGroupId.entrySet()) {
      String id = entry.getKey();
      for (StopSequence sequence : entry.getValue())
        stopSequenceGroupIds.put(sequence, id);
    }

    for (StopSequence sequence : sequenceStats.keySet()) {

      String groupId = stopSequenceGroupIds.get(sequence);

      for (BlockTripEntry trip : sequence.getTrips()) {

        BlockTripEntry prevTrip = trip.getPreviousTrip();

        if (prevTrip == null)
          continue;

        StopSequence prevSequence = stopSequencesByTrip.get(prevTrip);

        // No continuations if incoming is not part of the sequence collection
        if (prevSequence == null)
          continue;

        // No continuation if it's the same stop sequence
        if (prevSequence.equals(sequence))
          continue;

        // No contination if the the block group ids don't match
        String prevGroupId = stopSequenceGroupIds.get(prevSequence);
        if (!groupId.equals(prevGroupId))
          continue;

        StopEntry prevStop = prevSequence.getStops().get(
            prevSequence.getStops().size() - 1);
        StopEntry nextStop = sequence.getStops().get(0);
        double d = SphericalGeometryLibrary.distance(prevStop.getStopLat(),
            prevStop.getStopLon(), nextStop.getStopLat(), nextStop.getStopLon());
        if (d < 5280 / 4) {
          /*
           * System.out.println("distance=" + d + " from=" + prevStop.getId() +
           * " to=" + nextStop.getId() + " ssFrom=" + prevSequence.getId() +
           * " ssTo=" + stopSequence.getId());
           */
          PatternStats stats = sequenceStats.get(prevSequence);
          stats.continuations.add(sequence);
        }
      }
    }
  }

  private void exploreStopSequences(RecursiveStats rs,
      Map<StopSequence, PatternStats> patternStats,
      Iterable<StopSequence> patterns, String depth) {

    Segment prevSegment = rs.prevSegment;

    for (StopSequence pattern : patterns) {

      if (rs.visited.contains(pattern))
        continue;

      PatternStats stats = patternStats.get(pattern);

      double count = stats.tripCounts;
      double ratio = count / rs.maxTripCount;

      if (ratio < SERVICE_PATTERN_TRIP_COUNT_RATIO_MIN)
        continue;

      Segment segment = stats.segment;

      if (prevSegment != null)
        segment = new Segment(prevSegment, segment, prevSegment.distance
            + segment.distance);

      rs.longestSegment.add(segment.distance, segment);

      Set<StopSequence> nextPatterns = stats.continuations;

      if (!nextPatterns.isEmpty()) {
        rs.visited.add(pattern);
        rs.prevSegment = segment;
        exploreStopSequences(rs, patternStats, nextPatterns, depth + "  ");
        rs.visited.remove(pattern);
      }
    }
  }

  private double getMaxCommonStopSequenceRatio(StopSequence a, StopSequence b) {
    Set<Pair<StopEntry>> pairsA = getStopSequenceAsStopPairSet(a);
    Set<Pair<StopEntry>> pairsB = getStopSequenceAsStopPairSet(b);
    int common = 0;
    for (Pair<StopEntry> pairA : pairsA) {
      if (pairsB.contains(pairA))
        common++;
    }

    double ratioA = ((double) common) / pairsA.size();
    double ratioB = ((double) common) / pairsB.size();
    return Math.max(ratioA, ratioB);
  }

  private Set<Pair<StopEntry>> getStopSequenceAsStopPairSet(
      StopSequence stopSequence) {
    Set<Pair<StopEntry>> pairs = new HashSet<Pair<StopEntry>>();
    StopEntry prev = null;
    for (StopEntry stop : stopSequence.getStops()) {
      if (prev != null) {
        Pair<StopEntry> pair = Tuples.pair(prev, stop);
        pairs.add(pair);
      }
      prev = stop;
    }
    return pairs;
  }

  /*
   * private static class BlockComparator implements Comparator<Trip> {
   *
   * public BlockComparator() {
   *
   * }
   *
   * public int compare(Trip o1, Trip o2) { return o2.getBlockSequenceId() -
   * o1.getBlockSequenceId(); } }
   */

  private static class PatternStats {
    long tripCounts;
    Segment segment;
    Set<StopSequence> continuations = new HashSet<StopSequence>();
  }

  private static class RecursiveStats {
    Max<Segment> longestSegment = new Max<Segment>();
    Set<StopSequence> visited = new HashSet<StopSequence>();
    long maxTripCount;
    Segment prevSegment;
  }

  private static class Segment {

    double fromLon;
    double fromLat;
    double toLon;
    double toLat;
    double distance;

    public Segment() {

    }

    public Segment(Segment prevSegment, Segment toSegment, double d) {
      this.fromLat = prevSegment.fromLat;
      this.fromLon = prevSegment.fromLon;
      this.toLat = toSegment.toLat;
      this.toLon = toSegment.toLon;
      this.distance = d;
    }
  }
}
TOP

Related Classes of org.onebusaway.transit_data_federation.impl.StopSequenceCollectionServiceImpl$PatternStats

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.