| 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
 | module TomTom
  class Matrix
    def initialize(connection)
      @connection = connection
    end
    def matrix(way_costs)
      points_with_ids = points_from_way_costs(way_costs)
      points = points_as_params(points_with_ids)
      Rails.logger.info "Invoke TomTom for #{points.size} points"
      response = @connection.post do |req|
        req.url '/routing/1/matrix/json'
        req.headers['Content-Type'] = 'application/json'
        req.params[:routeType] = 'shortest'
        req.params[:travelMode] = 'bus'
        req.body = build_request_body(points)
      end
      matrix_json = JSON.parse(response.body)
      check_for_error_response(matrix_json)
      extract_costs_to_way_costs!(
        way_costs,
        points_with_ids,
        matrix_json
      )
    rescue RemoteError => e
      Rails.logger.error "TomTom::Matrix server error: #{e}"
      []
    end
    def points_from_way_costs(way_costs)
      points = []
      way_costs.each do |way_cost|
        departure_id, arrival_id = way_cost.id.split('-')
        departure = TomTom::Matrix::Point.new(
          way_cost.departure,
          departure_id
        )
        arrival = TomTom::Matrix::Point.new(
          way_cost.arrival,
          arrival_id
        )
        # Don't add duplicate coordinates. This assumes that
        # `way_costs` consists of an ordered route of points where
        # each departure coordinate is the same as the preceding
        # arrival coordinate.
        if points.empty? ||
            points.last.coordinates != departure.coordinates
          points << departure
        end
        points << arrival
      end
      points
    end
    def points_as_params(points)
      points.map do |point|
        {
          point: {
            latitude: point.coordinates.lat,
            longitude: point.coordinates.lng
          }
        }
      end
    end
    def build_request_body(points)
      # Serialize `BigDecimal` values as floats to please the TomTom API
      RequestJSONSerializer.dump({
        origins: points,
        destinations: points
      })
    end
    def check_for_error_response(matrix_json)
      if matrix_json.has_key?('error')
        raise RemoteError, matrix_json['error']['description']
      end
    end
    def extract_costs_to_way_costs!(way_costs, points, matrix_json)
      way_costs = []
      # `row` and `column` order is the same as `points`
      matrix_json['matrix'].each_with_index do |row, row_i|
        row.each_with_index do |column, column_i|
          next if column['statusCode'] != 200
          distance = column['response']['routeSummary']['lengthInMeters']
          # Ignore costs between a point and itself (e.g. from A to A)
          next if distance == 0
          departure = points[row_i]
          arrival = points[column_i]
          way_costs << WayCost.new(
            departure: Geokit::LatLng.new(
              departure.coordinates.lat,
              departure.coordinates.lng
            ),
            arrival: Geokit::LatLng.new(
              arrival.coordinates.lat,
              arrival.coordinates.lng
            ),
            distance: distance,
            time: column['response']['routeSummary']['travelTimeInSeconds'],
            id: "#{departure.id}-#{arrival.id}"
          )
        end
      end
      way_costs
    end
    class RemoteError < RuntimeError; end
  end
end
 |