Skip to content

Commit

Permalink
Merge pull request #1044 from transitland/before-stop-first-only
Browse files Browse the repository at this point in the history
Before stop first only
  • Loading branch information
doublestranded authored Mar 31, 2017
2 parents 6f46d9f + a8788eb commit d4ae2d0
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 130 deletions.
197 changes: 75 additions & 122 deletions app/services/geometry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,94 +75,19 @@ def self.stop_after_geometry(stop_as_spherical, stop_as_cartesian, line_geometry
line_geometry_as_cartesian.after?(stop_as_cartesian) || OutlierStop.outlier_stop_from_precomputed_geometries(stop_as_spherical, stop_as_cartesian, line_geometry_as_cartesian)
end

def self.index_of_closest_match_line_segment(locators, s, e, point)
# the method is going forward along the line's direction to find the closest match.

closest_point_candidates = locators[s..e].map{ |loc| loc.interpolate_point(Stop::GEOFACTORY) }
closest_point_and_dist = closest_point_candidates.map{ |closest_point|
[closest_point, closest_point.distance(point)]
}.detect { |closest_point_and_dist| closest_point_and_dist[1] < FIRST_MATCH_THRESHOLD }

# Since the first match is within FIRST_MATCH_THRESHOLD, it might not be the best (closest)
# within the search range s through e.
# So here we're walking up the line until we can't find a closer match - the next local minimum.
unless closest_point_and_dist.nil?
dist = closest_point_and_dist[1]
i = closest_point_candidates.index(closest_point_and_dist[0])
unless i == locators[s..e].size - 1
next_locators = locators[s..e][i+1..-1].reject{|loc| loc.segment.single_point? }
closer_match = nil
next_locators.each_with_index do |loc, j|
next_seg_dist = loc.interpolate_point(Stop::GEOFACTORY).distance(point)
if next_seg_dist <= dist
closer_match = j
dist = next_seg_dist
else
break
end
end
i = locators[s..e].index(next_locators[closer_match]) unless closer_match.nil?
end
return s + i
end

# If no match is found within the threshold, just take closest match within the search range.
self.index_of_line_segment_with_nearest_point(locators, s, e)
end

def self.index_of_line_segment_with_nearest_point(locators, start, stop)
# finding the global closest within the start and stop range
distances_from_segs = locators[start..stop].map(&:distance_from_segment)
start + distances_from_segs.index(distances_from_segs.min)
end

def self.fallback_distances(rsp, stops=nil)
rsp.stop_distances = [0.0]
total_distance = 0.0
stops = rsp.stop_pattern.map {|onestop_id| Stop.find_by_onestop_id!(onestop_id) } if stops.nil?
stops.each_cons(2) do |stop1, stop2|
total_distance += stop1[:geometry].distance(stop2[:geometry])
rsp.stop_distances << total_distance
end
rsp.stop_distances.map!{ |distance| distance.round(DISTANCE_PRECISION) }
end

def self.gtfs_shape_dist_traveled(rsp, stop_times, tl_stops, shape_distances_traveled)
# assumes stop times and shapes BOTH have shape_dist_traveled, and they're in the same units
# assumes the line geometry is not generated, and shape_points equals the rsp geometry.
rsp.stop_distances = []
search_and_seg_index = 0
stop_times.each_with_index do |st, i|
stop_onestop_id = rsp.stop_pattern[i]

if st.shape_dist_traveled.to_f < shape_distances_traveled[0]
rsp.stop_distances << 0.0
elsif st.shape_dist_traveled.to_f > shape_distances_traveled[-1]
rsp.stop_distances << rsp[:geometry].length
else
# Find segment along shape points where stop shape_dist_traveled is between the two shape points' shape_dist_traveled
# need to account for stops matching to same segment
j = -1
dist1, dist2 = shape_distances_traveled[search_and_seg_index..-1].each_cons(2).detect do |d1, d2|
j += 1
st.shape_dist_traveled.to_f >= d1 && st.shape_dist_traveled.to_f <= d2
end

search_and_seg_index = search_and_seg_index + j

if dist1.nil? || dist2.nil?
raise StandardError.new("Problem finding stop distance for Stop #{stop_onestop_id}, number #{i + 1} of RSP #{rsp.onestop_id} using shape_dist_traveled")
else
route_line_as_cartesian = self.cartesian_cast(rsp[:geometry])
stop = tl_stops[i]
locators = route_line_as_cartesian.locators(self.cartesian_cast(stop[:geometry]))
seg_dist = st.shape_dist_traveled.to_f - shape_distances_traveled[search_and_seg_index]
point_on_line = locators[search_and_seg_index].interpolate_point(RGeo::Cartesian::Factory.new(srid: 4326), seg_dist=seg_dist)
rsp.stop_distances << LineString.distance_along_line_to_nearest_point(route_line_as_cartesian, point_on_line, search_and_seg_index)
end
end
def self.index_of_line_segment_with_nearest_point_next_stop(route_line_as_cartesian, tl_stops, test_stop_index, start_seg_index, num_segments)
if (test_stop_index <= tl_stops.size - 1)
next_stop_as_cartesian = self.cartesian_cast(tl_stops[test_stop_index][:geometry])
next_stop_locators = route_line_as_cartesian.locators(next_stop_as_cartesian)
return self.index_of_line_segment_with_nearest_point(next_stop_locators, start_seg_index, num_segments - 1)
else
return num_segments - 1
end
rsp.stop_distances.map!{ |distance| distance.round(DISTANCE_PRECISION) }
end

def self.calculate_distances(rsp, stops=nil)
Expand All @@ -181,67 +106,45 @@ def self.calculate_distances(rsp, stops=nil)
b = 0
c = num_segments - 1
last_stop_after_geom = self.stop_after_geometry(stops[-1][:geometry], self.cartesian_cast(stops[-1][:geometry]), route_line_as_cartesian)
previous_stop_before_geom = false
stop_matched_inside_line = false
stops.each_index do |i|
current_stop = stops[i]
current_stop_as_spherical = current_stop[:geometry]
current_stop_as_cartesian = self.cartesian_cast(current_stop_as_spherical)
if i == 0 && self.stop_before_geometry(current_stop_as_spherical, current_stop_as_cartesian, route_line_as_cartesian)
previous_stop_before_geom = true
rsp.stop_distances << 0.0
elsif i == stops.size - 1 && last_stop_after_geom
rsp.stop_distances << rsp[:geometry].length
else
if (i + 1 <= stops.size - 1)
next_stop = stops[i+1]
next_stop_as_cartesian = self.cartesian_cast(next_stop[:geometry])
next_stop_locators = route_line_as_cartesian.locators(next_stop_as_cartesian)
c = self.index_of_line_segment_with_nearest_point(next_stop_locators, a, num_segments-1)
else
c = num_segments - 1
end

c = self.index_of_line_segment_with_nearest_point_next_stop(route_line_as_cartesian, stops, i+1, a, num_segments)
locators = route_line_as_cartesian.locators(current_stop_as_cartesian)
b = self.index_of_closest_match_line_segment(locators, a, c, current_stop_as_cartesian)
b = self.index_of_line_segment_with_nearest_point(locators, a, c)
nearest_point = LineString.nearest_point_on_line(locators, b)

# The next stop's match may be too early and restrictive, so allow more segment possibilities
if LineString.distance_to_nearest_point_on_line(current_stop_as_spherical, nearest_point) > FIRST_MATCH_THRESHOLD
if (i + 2 <= stops.size - 1)
next_stop = stops[i+2]
next_stop_as_cartesian = self.cartesian_cast(next_stop[:geometry])
next_stop_locators = route_line_as_cartesian.locators(next_stop_as_cartesian)
c = self.index_of_line_segment_with_nearest_point(next_stop_locators, a, num_segments-1)
else
c = num_segments - 1
end
b = self.index_of_closest_match_line_segment(locators, a, c, current_stop_as_cartesian)
c = self.index_of_line_segment_with_nearest_point_next_stop(route_line_as_cartesian, stops, i+2, a, num_segments)
b = self.index_of_line_segment_with_nearest_point(locators, a, c)
nearest_point = LineString.nearest_point_on_line(locators, b)
end

distance = LineString.distance_along_line_to_nearest_point(route_line_as_cartesian, nearest_point, b)
if (i!=0)
if self.stop_before_geometry(current_stop_as_spherical, current_stop_as_cartesian, route_line_as_cartesian) && previous_stop_before_geom
previous_stop_before_geom = true
else
equivalent_stop = stops[i].onestop_id.eql?(stops[i-1].onestop_id) || stops[i][:geometry].eql?(stops[i-1][:geometry])
if !equivalent_stop && !previous_stop_before_geom
# this can happen if this stop matches to the same segment as the previous
if (distance <= rsp.stop_distances[i-1])
a += 1
if (a == num_segments - 1)
distance = rsp[:geometry].length
elsif (a > c)
# we should leave the faulty distance here (unless the interpolation tries to correct it)
# because something might be wrong with the RouteStopPattern.
else
b = self.index_of_closest_match_line_segment(locators, a, c, current_stop_as_cartesian)
nearest_point = LineString.nearest_point_on_line(locators, b)
distance = LineString.distance_along_line_to_nearest_point(route_line_as_cartesian, nearest_point, b)
end
equivalent_stop = stops[i].onestop_id.eql?(stops[i-1].onestop_id) || stops[i][:geometry].eql?(stops[i-1][:geometry])
if !equivalent_stop
# this can happen if this stop matches to the same segment as the previous
if (distance <= rsp.stop_distances[i-1]) && stop_matched_inside_line
a += 1
if (a == num_segments - 1)
distance = rsp[:geometry].length
elsif (a > c)
# Something might be wrong with the RouteStopPattern.
else
b = self.index_of_line_segment_with_nearest_point(locators, a, c)
nearest_point = LineString.nearest_point_on_line(locators, b)
distance = LineString.distance_along_line_to_nearest_point(route_line_as_cartesian, nearest_point, b)
end
end
previous_stop_before_geom = false
end
end

Expand All @@ -258,10 +161,60 @@ def self.calculate_distances(rsp, stops=nil)
else
rsp.stop_distances << distance
end
stop_matched_inside_line = true if !nearest_point.eql?(route_line_as_cartesian.points.first)
a = b
end
end # end stop pattern loop
rsp.stop_distances.map!{ |distance| distance.round(DISTANCE_PRECISION) }
end

def self.fallback_distances(rsp, stops=nil)
rsp.stop_distances = [0.0]
total_distance = 0.0
stops = rsp.stop_pattern.map {|onestop_id| Stop.find_by_onestop_id!(onestop_id) } if stops.nil?
stops.each_cons(2) do |stop1, stop2|
total_distance += stop1[:geometry].distance(stop2[:geometry])
rsp.stop_distances << total_distance
end
rsp.stop_distances.map!{ |distance| distance.round(DISTANCE_PRECISION) }
end

def self.gtfs_shape_dist_traveled(rsp, stop_times, tl_stops, shape_distances_traveled)
# assumes stop times and shapes BOTH have shape_dist_traveled, and they're in the same units
# assumes the line geometry is not generated, and shape_points equals the rsp geometry.
rsp.stop_distances = []
search_and_seg_index = 0
stop_times.each_with_index do |st, i|
stop_onestop_id = rsp.stop_pattern[i]

if st.shape_dist_traveled.to_f < shape_distances_traveled[0]
rsp.stop_distances << 0.0
elsif st.shape_dist_traveled.to_f > shape_distances_traveled[-1]
rsp.stop_distances << rsp[:geometry].length
else
# Find segment along shape points where stop shape_dist_traveled is between the two shape points' shape_dist_traveled
# need to account for stops matching to same segment
j = -1
dist1, dist2 = shape_distances_traveled[search_and_seg_index..-1].each_cons(2).detect do |d1, d2|
j += 1
st.shape_dist_traveled.to_f >= d1 && st.shape_dist_traveled.to_f <= d2
end

search_and_seg_index = search_and_seg_index + j

if dist1.nil? || dist2.nil?
raise StandardError.new("Problem finding stop distance for Stop #{stop_onestop_id}, number #{i + 1} of RSP #{rsp.onestop_id} using shape_dist_traveled")
else
route_line_as_cartesian = self.cartesian_cast(rsp[:geometry])
stop = tl_stops[i]
locators = route_line_as_cartesian.locators(self.cartesian_cast(stop[:geometry]))
seg_dist = st.shape_dist_traveled.to_f - shape_distances_traveled[search_and_seg_index]
point_on_line = locators[search_and_seg_index].interpolate_point(RGeo::Cartesian::Factory.new(srid: 4326), seg_dist=seg_dist)
rsp.stop_distances << LineString.distance_along_line_to_nearest_point(route_line_as_cartesian, point_on_line, search_and_seg_index)
end
end
end
rsp.stop_distances.map!{ |distance| distance.round(DISTANCE_PRECISION) }
end
end
end
3 changes: 1 addition & 2 deletions app/services/gtfs_graph.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,7 @@ def calculate_rsp_distances(rsp, stops, shape_distances_traveled, stop_times)
# edited rsps will probably have a shape
Geometry::DistanceCalculation.fallback_distances(rsp, stops=stops)
elsif (rsp.stop_distances.compact.empty? || rsp.issues.map(&:issue_type).include?(:distance_calculation_inaccurate))
# avoid writing over stop distances computed with shape_dist_traveled, or already computed somehow -
# unless if rsps have inaccurate stop distances, we'll allow a recomputation if there's a fix in place.
# avoid writing over stop distances that have been computed already and have no issues.
Geometry::DistanceCalculation.calculate_distances(rsp, stops=stops)
end
rescue => e
Expand Down
19 changes: 19 additions & 0 deletions spec/factories/feed_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,25 @@
end
end

factory :feed_mbta, parent: :feed, class: Feed do
onestop_id 'f-drt-mbta'
url 'http://www.mbta.com/uploadedfiles/MBTA_GTFS.zip'
version 1
after :create do |feed, evaluator|
operator = create(
:operator,
name: 'MBTA',
onestop_id: 'o-drt-mbta',
timezone: 'America/New_York',
website: 'http://www.google.com',
)
feed.operators_in_feed.create(
operator: operator,
gtfs_agency_id: '1'
)
end
end

factory :feed_grand_river, parent: :feed, class: Feed do
onestop_id 'f-dpwz-grandrivertransit'
url 'http://www.regionofwaterloo.ca/opendatadownloads/GRT_GTFS.zip'
Expand Down
10 changes: 10 additions & 0 deletions spec/factories/feed_version_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@
association :feed, factory: :feed_nj_path
end

factory :feed_version_mbta_33884627 do
file { File.open(Rails.root.join('spec/support/example_gtfs_archives/mbta_trip_33884627.zip')) }
association :feed, factory: :feed_mbta
end

factory :feed_version_marta_trip_5449755 do
file { File.open(Rails.root.join('spec/support/example_gtfs_archives/marta_trip_5449755.zip')) }
association :feed, factory: :feed_marta
end

factory :feed_version_marta do
file { File.open(Rails.root.join('spec/support/example_gtfs_archives/marta-trip-5453552.zip')) }
association :feed, factory: :feed_marta
Expand Down
Loading

0 comments on commit d4ae2d0

Please sign in to comment.