1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
|
module Chouette
class Route < Chouette::TridentActiveRecord
has_metadata
include RouteRestrictions
include ChecksumSupport
include ObjectidSupport
extend Enumerize
if ENV["CHOUETTE_ROUTE_POSITION_CHECK"] == "true" || !Rails.env.production?
after_commit do
positions = stop_points.pluck(:position)
Rails.logger.debug "Check positions in Route #{id} : #{positions.inspect}"
if positions.size != positions.uniq.size
raise "DUPLICATED stop_points positions in Route #{id} : #{positions.inspect}"
end
end
end
enumerize :direction, in: %i(straight_forward backward clockwise counter_clockwise north north_west west south_west south south_east east north_east)
enumerize :wayback, in: %i(outbound inbound), default: :outbound
def self.nullable_attributes
[:published_name, :comment, :number, :name, :direction, :wayback]
end
belongs_to :line
belongs_to :opposite_route, :class_name => 'Chouette::Route', :foreign_key => :opposite_route_id
has_many :routing_constraint_zones
has_many :journey_patterns, :dependent => :destroy
has_many :vehicle_journeys, :dependent => :destroy do
def timeless
Chouette::Route.vehicle_journeys_timeless(proxy_association.owner.journey_patterns.pluck( :departure_stop_point_id))
end
end
has_many :vehicle_journey_at_stops, through: :vehicle_journeys
has_many :vehicle_journey_frequencies, :dependent => :destroy do
# Todo : I think there is a better way to do this.
def timeless
Chouette::Route.vehicle_journeys_timeless(proxy_association.owner.journey_patterns.pluck( :departure_stop_point_id))
end
end
has_many :stop_points, -> { order("position") }, :dependent => :destroy do
def find_by_stop_area(stop_area)
stop_area_ids = Integer === stop_area ? [stop_area] : (stop_area.children_in_depth + [stop_area]).map(&:id)
where( :stop_area_id => stop_area_ids).first or
raise ActiveRecord::RecordNotFound.new("Can't find a StopArea #{stop_area.inspect} in Route #{proxy_owner.id.inspect}'s StopPoints")
end
def between(departure, arrival)
between_positions = [departure, arrival].collect do |endpoint|
case endpoint
when Chouette::StopArea
find_by_stop_area(endpoint).position
when Chouette::StopPoint
endpoint.position
when Integer
endpoint
else
raise ActiveRecord::RecordNotFound.new("Can't determine position in route #{proxy_owner.id} with #{departure.inspect}")
end
end
where(" position between ? and ? ", between_positions.first, between_positions.last)
end
end
has_many :vehicle_journey_at_stops, through: :vehicle_journeys
has_many :stop_areas, -> { order('stop_points.position ASC') }, :through => :stop_points do
def between(departure, arrival)
departure, arrival = [departure, arrival].map do |endpoint|
String === endpoint ? Chouette::StopArea.find_by_objectid(endpoint) : endpoint
end
proxy_owner.stop_points.between(departure, arrival).includes(:stop_area).collect(&:stop_area)
end
end
has_many :time_tables, :through => :vehicle_journeys
has_many :purchase_windows, :through => :vehicle_journeys
accepts_nested_attributes_for :stop_points, :allow_destroy => :true
validates_presence_of :name
validates_presence_of :published_name
validates_presence_of :line
validates :wayback, inclusion: { in: self.wayback.values }
after_commit :calculate_costs!,
on: [:create, :update],
if: ->() {
# Ensure the call back doesn't run during a referential merge
!referential.in_referential_suite? &&
TomTom.enabled?
}
def clean!
vehicle_journeys.find_each do |vj|
vj.vehicle_journey_at_stops.delete_all
end
vehicle_journeys.delete_all
journey_patterns.delete_all
stop_points.delete_all
routing_constraint_zones.delete_all
Chouette::Route.where(opposite_route_id: self.id).update_all(opposite_route_id: nil)
self.delete
end
def duplicate opposite=false
overrides = {
'opposite_route_id' => nil,
'name' => I18n.t('activerecord.copy', name: self.name)
}
keys_for_create = attributes.keys - %w{id objectid created_at updated_at}
atts_for_create = attributes
.slice(*keys_for_create)
.merge(overrides)
if opposite
atts_for_create[:wayback] = self.opposite_wayback
atts_for_create[:name] = I18n.t('routes.opposite', name: self.name)
atts_for_create[:published_name] = atts_for_create[:name]
atts_for_create[:opposite_route_id] = self.id
end
new_route = self.class.create!(atts_for_create)
duplicate_stop_points(for_route: new_route, opposite: opposite)
new_route
end
def duplicate_stop_points(for_route:, opposite: false)
stop_points.each(&duplicate_stop_point(for_route: for_route, opposite: opposite))
end
def duplicate_stop_point(for_route:, opposite: false)
-> stop_point do
stop_point.duplicate(for_route: for_route, opposite: opposite)
end
end
def local_id
"IBOO-#{self.referential.id}-#{self.line.get_objectid.local_id}-#{self.id}"
end
def geometry_presenter
Chouette::Geometry::RoutePresenter.new self
end
@@opposite_waybacks = { outbound: :inbound, inbound: :outbound}
def opposite_wayback
@@opposite_waybacks[wayback.to_sym]
end
def opposite_route_candidates
if opposite_wayback
line.routes.where(opposite_route: [nil, self], wayback: opposite_wayback)
else
self.class.none
end
end
validate :check_opposite_route
def check_opposite_route
return unless opposite_route && opposite_wayback
unless opposite_route_candidates.include?(opposite_route)
errors.add(:opposite_route_id, :invalid)
end
end
def checksum_attributes
values = self.slice(*['name', 'published_name', 'wayback']).values
values.tap do |attrs|
attrs << self.stop_points.sort_by(&:position).map{|sp| [sp.stop_area.user_objectid, sp.for_boarding, sp.for_alighting]}
attrs << self.routing_constraint_zones.map(&:checksum).uniq.sort
end
end
has_checksum_children StopPoint
has_checksum_children RoutingConstraintZone
def geometry
points = stop_areas.map(&:to_lat_lng).compact.map do |loc|
[loc.lng, loc.lat]
end
GeoRuby::SimpleFeatures::LineString.from_coordinates( points, 4326)
end
def time_tables
ids = vehicle_journeys.joins(:time_tables).pluck('time_tables.id').uniq
Chouette::TimeTable.where(id: ids)
end
def sorted_vehicle_journeys(journey_category_model)
send(journey_category_model)
.joins(:journey_pattern, :vehicle_journey_at_stops)
.joins('LEFT JOIN "time_tables_vehicle_journeys" ON "time_tables_vehicle_journeys"."vehicle_journey_id" = "vehicle_journeys"."id" LEFT JOIN "time_tables" ON "time_tables"."id" = "time_tables_vehicle_journeys"."time_table_id"')
.where("vehicle_journey_at_stops.stop_point_id=journey_patterns.departure_stop_point_id")
.order("vehicle_journey_at_stops.departure_time")
end
def stop_point_permutation?( stop_point_ids)
stop_points.map(&:id).map(&:to_s).sort == stop_point_ids.map(&:to_s).sort
end
def reorder!( stop_point_ids)
return false unless stop_point_permutation?( stop_point_ids)
stop_area_id_by_stop_point_id = {}
stop_points.each do |sp|
stop_area_id_by_stop_point_id.merge!( sp.id => sp.stop_area_id)
end
reordered_stop_area_ids = []
stop_point_ids.each do |stop_point_id|
reordered_stop_area_ids << stop_area_id_by_stop_point_id[ stop_point_id.to_i]
end
stop_points.each_with_index do |sp, index|
if sp.stop_area_id.to_s != reordered_stop_area_ids[ index].to_s
#result = sp.update_attributes( :stop_area_id => reordered_stop_area_ids[ index])
sp.stop_area_id = reordered_stop_area_ids[ index]
result = sp.save!
end
end
return true
end
def full_journey_pattern
journey_pattern = journey_patterns.find_or_create_by registration_number: self.number, name: self.name
journey_pattern.stop_points = self.stop_points
journey_pattern
end
def calculate_costs!
RouteWayCostWorker.perform_async(referential.id, id)
end
protected
def self.vehicle_journeys_timeless(stop_point_id)
all( :conditions => ['vehicle_journeys.id NOT IN (?)', Chouette::VehicleJourneyAtStop.where(stop_point_id: stop_point_id).pluck(:vehicle_journey_id)] )
end
end
end
|