aboutsummaryrefslogtreecommitdiffstats
path: root/lib/tom_tom/matrix.rb
blob: e779abbf7144918295379d292ecd799dd278bc4b (plain)
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
module TomTom
  class Matrix
    def initialize(connection)
      @connection = connection
    end

    # Exceptions:
    #
    # * This raises a `TomTom::Errors::MatrixRemoteError` when the API responds
    #   with an error.
    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[:traffic] = 'false'
        req.params[:travelMode] = 'bus'

        req.body = build_request_body(points)
      end

      check_for_error_response(response)

      extract_costs_to_way_costs!(
        way_costs,
        points_with_ids,
        JSON.parse(response.body)
      )
    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(response)
      if response.status != 200
        raise TomTom::Errors::MatrixRemoteError,
          "status: #{response.status}, body: #{response.body}"
      end

      json = JSON.parse(response.body)

      if json.has_key?('error')
        raise TomTom::Errors::MatrixRemoteError,
          "status: #{response.status}, message: #{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
  end
end