From f78ecb3113cdc51a9b74cd73d6495584d8dfe20f Mon Sep 17 00:00:00 2001 From: Harshal Jaitpal Date: Thu, 30 Jun 2022 11:16:24 +0530 Subject: [PATCH] CL-25818, Upgraded geocoder version and fixed calculate_route, lookup and autocomplete API (#5) * CL-25743, Upgraded geocoder version and changed code accordingly * Resolved comments and refactored code * Adding Flexible polyline decoding logic to return route results with lat, lng --- Gemfile.lock | 8 +- geocoder_here_maps.gemspec | 2 +- .../concerns/here_lookup_default_methods.rb | 6 +- lib/geocoder/lookups/here_calculate_route.rb | 17 ++-- lib/geocoder/lookups/here_lookup_id.rb | 25 +++++ lib/geocoder/lookups/here_suggest.rb | 20 ++-- .../concerns/flexible_polyline_decoder.rb | 97 +++++++++++++++++++ lib/geocoder/results/here_calculate_route.rb | 62 ++++++++---- lib/geocoder/results/here_lookup_id.rb | 12 +++ lib/geocoder/results/here_suggest.rb | 16 ++- lib/geocoder_here_maps.rb | 7 +- lib/geocoder_here_maps/here.rb | 13 --- lib/geocoder_here_maps/version.rb | 2 +- 13 files changed, 220 insertions(+), 67 deletions(-) create mode 100644 lib/geocoder/lookups/here_lookup_id.rb create mode 100644 lib/geocoder/results/concerns/flexible_polyline_decoder.rb create mode 100644 lib/geocoder/results/here_lookup_id.rb delete mode 100644 lib/geocoder_here_maps/here.rb diff --git a/Gemfile.lock b/Gemfile.lock index 77a05a1..2c1282e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,14 +1,14 @@ PATH remote: . specs: - geocoder_here_maps (0.3.0) - geocoder (>= 1.5.1, < 1.7.0) + geocoder_here_maps (0.3.1) + geocoder (>= 1.8.0) GEM remote: https://rubygems.org/ specs: diff-lcs (1.3) - geocoder (1.6.3) + geocoder (1.8.0) rake (10.5.0) rspec (3.8.0) rspec-core (~> 3.8.0) @@ -34,4 +34,4 @@ DEPENDENCIES rspec (~> 3.0) BUNDLED WITH - 1.17.1 + 1.17.3 diff --git a/geocoder_here_maps.gemspec b/geocoder_here_maps.gemspec index 73febc2..710b688 100644 --- a/geocoder_here_maps.gemspec +++ b/geocoder_here_maps.gemspec @@ -26,5 +26,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "rspec", "~> 3.0" - spec.add_dependency "geocoder", ">= 1.5.1", "< 1.7.0" + spec.add_dependency "geocoder", ">= 1.8.0" end diff --git a/lib/geocoder/lookups/concerns/here_lookup_default_methods.rb b/lib/geocoder/lookups/concerns/here_lookup_default_methods.rb index 5d4f382..f13d04a 100644 --- a/lib/geocoder/lookups/concerns/here_lookup_default_methods.rb +++ b/lib/geocoder/lookups/concerns/here_lookup_default_methods.rb @@ -18,13 +18,13 @@ def supported_protocols def query_url_here_options(query) options = { - apikey: configuration.api_key, - language: (query.language || configuration.language) + apiKey: configuration.api_key, + lang: (query.language || configuration.language) } end def domain - 'ls.hereapi.com' + 'hereapi.com' end end end diff --git a/lib/geocoder/lookups/here_calculate_route.rb b/lib/geocoder/lookups/here_calculate_route.rb index b6a666b..2e1682b 100644 --- a/lib/geocoder/lookups/here_calculate_route.rb +++ b/lib/geocoder/lookups/here_calculate_route.rb @@ -9,14 +9,14 @@ def name private def base_query_url(query) - "#{protocol}://route.#{domain}/routing/7.2/calculateroute.json?" + "#{protocol}://router.#{domain}/v8/routes?" end def results(query) return [] unless doc = fetch_data(query) - return [] unless doc["response"] && doc["response"]["route"] + return [] unless doc["routes"] - r = doc["response"]["route"] + r = doc["routes"] if r.is_a?(Array) return r @@ -26,15 +26,12 @@ def results(query) end def query_url_params(query) - super.merge(query_url_here_options(query)).merge( - waypoints_hash(query.to_param) - ) + super.merge(query_url_here_options(query)) end - def waypoints_hash(waypoints) - waypoints.each_with_object({}).with_index do |(point, hash), index| - hash["waypoint#{index}"] = point.join(',') - end + def hash_to_query(hash) + URI.encode_www_form(hash) end + end end diff --git a/lib/geocoder/lookups/here_lookup_id.rb b/lib/geocoder/lookups/here_lookup_id.rb new file mode 100644 index 0000000..331d8db --- /dev/null +++ b/lib/geocoder/lookups/here_lookup_id.rb @@ -0,0 +1,25 @@ +module Geocoder::Lookup + class HereLookupId < Base + include HereLookupDefaultMethods + + def name + "HereLookupId" + end + + private + + def base_query_url(query) + "#{protocol}://lookup.search.#{domain}/v1/lookup?" + end + + def results(query) + return [] unless item = fetch_data(query) + [item].compact.presence || [] + end + + def query_url_params(query) + params = {id: super[:location_id]} + params.merge(query_url_here_options(query)) + end + end +end diff --git a/lib/geocoder/lookups/here_suggest.rb b/lib/geocoder/lookups/here_suggest.rb index 3e0ed92..de2729c 100644 --- a/lib/geocoder/lookups/here_suggest.rb +++ b/lib/geocoder/lookups/here_suggest.rb @@ -1,22 +1,27 @@ +require 'geocoder/lookups/here' + module Geocoder::Lookup - class HereSuggest < Base - include HereLookupDefaultMethods + class HereSuggest < Here def name "HereSuggest" end + def handle + :here + end + private def base_query_url(query) - "#{protocol}://autocomplete.geocoder.#{domain}/6.2/suggest.json?" + "#{protocol}://autocomplete.search.hereapi.com/v1/autocomplete?" end def results(query) return [] unless doc = fetch_data(query) - return [] unless doc['suggestions'] + return [] unless doc['items'] - r = doc['suggestions'] + r = doc['items'] if r.is_a?(Array) return r @@ -25,10 +30,5 @@ def results(query) end end - def query_url_params(query) - super.merge(query_url_here_options(query)).merge( - query: query.sanitized_text - ) - end end end diff --git a/lib/geocoder/results/concerns/flexible_polyline_decoder.rb b/lib/geocoder/results/concerns/flexible_polyline_decoder.rb new file mode 100644 index 0000000..5852ead --- /dev/null +++ b/lib/geocoder/results/concerns/flexible_polyline_decoder.rb @@ -0,0 +1,97 @@ +module Geocoder::Result + class FlexiblePolylineDecoder + + DECODING_TABLE = [ + 62, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, + 0, 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, -1, -1, -1, -1, 63, -1, 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 + ] + FORMAT_VERSION = 1 + + # Reserved values 4 and 5 should not be selectable + THIRD_DIM_MAP = {1=> 'level', 2=> 'altitude', 3=> 'elevation', 6=> 'custom1', 7=> 'custom2'} + + def decode(encoded) + # Return an iterator over coordinates. The number of coordinates are 2 or 3 depending on the polyline content. + return unless encoded.present? + last_lat = last_lng = last_z = 0 + + decoder = decode_unsigned_values(encoded) + header = decode_header(decoder) + + factor_degree = 10.0 ** header[:precision] + factor_z = 10.0 ** header[:third_dim_precision] + third_dim = header[:third_dim] + third_dim_key = THIRD_DIM_MAP[third_dim] + + if third_dim.zero? + return decoder.each_slice(2).drop(1).map do |decoded_lat, decoded_lng| + last_lat += to_signed(decoded_lat) + last_lng += to_signed(decoded_lng) + [last_lat / factor_degree, last_lng / factor_degree] + end + else + return decoder.drop(2).each_slice(3).map do |decoded_lat, decoded_lng, decoded_z| + last_lat += to_signed(decoded_lat) + last_lng += to_signed(decoded_lng) + last_z += to_signed(decoded_z) + + { lat: (last_lat / factor_degree), + lng: (last_lng / factor_degree), + "#{third_dim_key}": (last_z / factor_z) + } + end + end + end + + private + + def decode_header(decoder) + # Decode the polyline header from an encoded_char. Returns a PolylineHeader object. + raise 'Invalid format version' if decoder.next != FORMAT_VERSION + + value = decoder.next + precision = value & 15 + value >>= 4 + third_dim = value & 7 + third_dim_precision = (value >> 3) & 15 + + { precision: precision, third_dim: third_dim, + third_dim_precision: third_dim_precision + } + end + + def decode_char(char) + # Decode a single char to the corresponding value + char_value = char.ord + DECODING_TABLE[char_value - 45] + end + + def to_signed(value) + # Decode the sign from an unsigned value + value = ~value unless (value & 1).zero? + value >>= 1 + end + + def decode_unsigned_values(encoded) + # Return an iterator over encoded unsigned values part of an encoded polyline + result = shift = 0 + + Enumerator.new do |y| + encoded.each_char do |char| + + value = decode_char(char) + result |= (value & 0x1F) << shift + + if (value & 0x20).zero? + y << result + result = shift = 0 + else + shift += 5 + end + end + end + end + end +end diff --git a/lib/geocoder/results/here_calculate_route.rb b/lib/geocoder/results/here_calculate_route.rb index efb501b..831e40f 100644 --- a/lib/geocoder/results/here_calculate_route.rb +++ b/lib/geocoder/results/here_calculate_route.rb @@ -15,46 +15,70 @@ def initialize(data) @cache_hit = nil end - def waypoint - @data['waypoint'] - end - - def leg - @data['leg'] + def polyline + sections.collect { |section| + section['polyline'] + } end def shape - @data['shape'] - end - - def maneuver - leg['maneuver'] + sections.flat_map { |section| + FlexiblePolylineDecoder.new.decode(section['polyline']) + } end - def mode - @data['mode'] + def transport_mode + transport_data['mode'] end def distance - summary_data['distance'] + sections.sum { |section| + length(section) + } end def travel_time - summary_data['travelTime'] + sections.sum { |section| + duration(section) + } end def traffic_time - summary_data['trafficTime'] + sections.sum { |section| + duration(section) + } end def base_time - summary_data['baseTime'] + sections.sum { |section| + base_duration(section) + } + end + + def sections + @data['sections'] end private # ---------------------------------------------------------------- - def summary_data - @data['summary'] + def duration(section) + summary_data(section)['duration'] + end + + def base_duration(section) + summary_data(section)['baseDuration'] + end + + def length(section) + summary_data(section)['length'] + end + + def summary_data(section) + section['summary'] + end + + def transport_data + @data['transport'] end end end diff --git a/lib/geocoder/results/here_lookup_id.rb b/lib/geocoder/results/here_lookup_id.rb new file mode 100644 index 0000000..4056a9c --- /dev/null +++ b/lib/geocoder/results/here_lookup_id.rb @@ -0,0 +1,12 @@ +module Geocoder::Result + class HereLookupId < Here + + def label(format = :full) + @data['title'] + end + + def location_id + @data['id'] + end + end +end diff --git a/lib/geocoder/results/here_suggest.rb b/lib/geocoder/results/here_suggest.rb index 9e842a5..231c0cc 100644 --- a/lib/geocoder/results/here_suggest.rb +++ b/lib/geocoder/results/here_suggest.rb @@ -1,7 +1,7 @@ module Geocoder::Result class HereSuggest < Base - def address(format = :full) - @data['label'] + def label(format = :full) + @data['title'] end ## @@ -11,8 +11,16 @@ def coordinates fail end + def country_code + address_data['countryCode'] + end + def country - address_data['country'] + address_data['countryName'] + end + + def state_code + address_data['stateCode'] end def state @@ -32,7 +40,7 @@ def postal_code end def location_id - @data['locationId'] + @data['id'] end private # ---------------------------------------------------------------- diff --git a/lib/geocoder_here_maps.rb b/lib/geocoder_here_maps.rb index 5cb6077..735120c 100644 --- a/lib/geocoder_here_maps.rb +++ b/lib/geocoder_here_maps.rb @@ -1,7 +1,7 @@ require 'geocoder' require "geocoder_here_maps/version" require "geocoder/lookups/concerns/here_lookup_default_methods.rb" -require "geocoder_here_maps/here" +require "geocoder/results/concerns/flexible_polyline_decoder.rb" require "geocoder/lookups/here_suggest" require "geocoder/results/here_suggest" @@ -9,10 +9,13 @@ require "geocoder/lookups/here_calculate_route" require "geocoder/results/here_calculate_route" +require "geocoder/lookups/here_lookup_id" +require "geocoder/results/here_lookup_id" + module GeocoderHereMaps class << self def load - Geocoder::Lookup.street_services += %i[here_suggest here_calculate_route] + Geocoder::Lookup.street_services += %i[here_suggest here_calculate_route here_lookup_id] end end diff --git a/lib/geocoder_here_maps/here.rb b/lib/geocoder_here_maps/here.rb deleted file mode 100644 index b545dfa..0000000 --- a/lib/geocoder_here_maps/here.rb +++ /dev/null @@ -1,13 +0,0 @@ -require "geocoder/lookups/here" - -module Geocoder::Lookup - Here.class_eval do - include HereLookupDefaultMethods - - private - - def base_query_url(query) - "#{protocol}://#{if query.reverse_geocode? then 'reverse.' end}geocoder.#{domain}/6.2/#{if query.reverse_geocode? then 'reverse' end}geocode.json?" - end - end -end diff --git a/lib/geocoder_here_maps/version.rb b/lib/geocoder_here_maps/version.rb index 49e6a05..e6330b5 100644 --- a/lib/geocoder_here_maps/version.rb +++ b/lib/geocoder_here_maps/version.rb @@ -1,3 +1,3 @@ module GeocoderHereMaps - VERSION = "0.3.0" + VERSION = "0.3.1" end