Skip to content

Commit

Permalink
Merge branch 'feature/photon' of git://github.com/mainio/geocoder int…
Browse files Browse the repository at this point in the history
…o mainio-feature/photon
  • Loading branch information
alexreisner committed Sep 1, 2021
2 parents ae8f03a + 9fdb9eb commit f652a1a
Show file tree
Hide file tree
Showing 10 changed files with 479 additions and 1 deletion.
36 changes: 36 additions & 0 deletions README_API_GUIDE.md
Expand Up @@ -308,6 +308,42 @@ Open source geocoding engine which can be self-hosted. There are multiple servic
* **Limitations**: When using the free plan for a commercial product, a link back is required (see https://www.geoapify.com/geocoding-api/). Rate limit (requests/second) applied based on pricing plan. [Data licensed under Open Database License (ODbL) (you must provide attribution).](https://www.openstreetmap.org/copyright)
* **Notes**: To use Geoapify, set `Geocoder.configure(lookup: :geoapify, api_key: "your_api_key")`.

### Photon (`:photon`)

Open source geocoding engine which can be self-hosted. Komoot hosts a public installation for fair use without the need for an API key (usage might be subject of change).

* **API key**: none
* **Quota**: You can use the API for your project, but please be fair - extensive usage will be throttled.
* **Region**: world
* **SSL support**: yes
* **Languages**: en, de, fr, it
* **Extra query options** (see [Photon documentation](https://github.com/komoot/photon) for more information):
* `:limit` - restrict the maximum amount of returned results, e.g. `limit: 5`
* `:filter` - extra filters for the search
* `:bbox` (forward) - restricts the bounding box for the forward search,
e.g. `filter: { bbox: [9.5, 51.5, 11.5, 53.5] }`
(minLon, minLat, maxLon, maxLat).
* `:osm_tag` (forward) - filters forward search results by
[tags and values](https://taginfo.openstreetmap.org/projects/nominatim#tags),
e.g. `filter: { osm_tag: 'tourism:museum' }`,
see API documentation for more information.
* `:string` (reverse) - filters the reverse search results by a query
string filter, e.g. `filter: { string: 'query string filter' }`,
* `:bias` (forward) - a location bias based on which results are
prioritized, provide an option hash with the keys `:latitude`,
`:longitude`, and `:scale` (optional, default scale: 1.6), e.g.
`bias: { latitude: 12, longitude: 12, scale: 4 }`
* `:radius` (reverse) - a kilometer radius for the reverse geocoding search,
must be a positive number between 0-5000 (default radius: 1),
e.g. `radius: 10`
* `:distance_sort` (reverse) - defines if results are sorted by distance for
reverse search queries or not, only available if the distance sorting is
enabled for the instace, e.g. `distance_sort: true`
* **Documentation**: https://github.com/komoot/photon
* **Terms of Service**: https://photon.komoot.de/
* **Limitations**: The public API provider (Komoot) does not guarantee for the availability and usage might be subject of change in the future. You can host your own Photon server without such limitations. [Data licensed under Open Database License (ODbL) (you must provide attribution).](https://www.openstreetmap.org/copyright)
* **Notes**: To use Photon set `Geocoder.configure(lookup: :photon, use_https: true)`. If you are [running your own instance of Photon](https://github.com/komoot/photon) you can configure the host like this: `Geocoder.configure(lookup: :photon, use_https: true, photon: {host: "photon.example.org"})`.


Regional Street Address Lookups
-------------------------------
Expand Down
3 changes: 2 additions & 1 deletion lib/geocoder/lookup.rb
Expand Up @@ -63,7 +63,8 @@ def street_services
:amap,
:osmnames,
:amazon_location_service,
:geoapify
:geoapify,
:photon
]
end

Expand Down
90 changes: 90 additions & 0 deletions lib/geocoder/lookups/photon.rb
@@ -0,0 +1,90 @@
# frozen_string_literal: true

require 'geocoder/lookups/base'
require 'geocoder/results/photon'

module Geocoder
module Lookup
# https://github.com/komoot/photon
class Photon < Base
def name
'Photon'
end

private

def base_query_url(query)
host = configuration[:host] || 'photon.komoot.de'
method = query.reverse_geocode? ? 'reverse' : 'api'
"#{protocol}://#{host}/#{method}?"
end

def results(query)
return [] unless (doc = fetch_data(query))
return [] unless doc['type'] == 'FeatureCollection'
return [] unless doc['features'] || doc['features'].present?

doc['features']
end

def query_url_params(query)
lang = query.language || configuration.language
params = { lang: lang, limit: query.options[:limit] }

if query.reverse_geocode?
params.merge!(query_url_params_reverse(query))
else
params.merge!(query_url_params_coordinates(query))
end

params.merge!(super)
end

def query_url_params_coordinates(query)
params = { q: query.sanitized_text }

if (bias = query.options[:bias])
params.merge!(lat: bias[:latitude], lon: bias[:longitude], location_bias_scale: bias[:scale])
end

if (filter = query_url_params_coordinates_filter(query))
params.merge!(filter)
end

params
end

def query_url_params_coordinates_filter(query)
filter = query.options[:filter]
return unless filter

bbox = filter[:bbox]
{
bbox: bbox.is_a?(Array) ? bbox.join(',') : bbox,
osm_tag: filter[:osm_tag]
}
end

def query_url_params_reverse(query)
params = { lat: query.coordinates[0], lon: query.coordinates[1], radius: query.options[:radius] }

if (dsort = query.options[:distance_sort])
params[:distance_sort] = dsort ? 'true' : 'false'
end

if (filter = query_url_params_reverse_filter(query))
params.merge!(filter)
end

params
end

def query_url_params_reverse_filter(query)
filter = query.options[:filter]
return unless filter

{ query_string_filter: filter[:string] }
end
end
end
end
124 changes: 124 additions & 0 deletions lib/geocoder/results/photon.rb
@@ -0,0 +1,124 @@
# frozen_string_literal: true

require 'geocoder/results/base'

module Geocoder
module Result
# https://github.com/komoot/photon
class Photon < Base
def name
properties['name']
end

def address(_format = :full)
parts = []
parts << name if name
parts << street_address if street_address
parts << city
parts << state if state
parts << postal_code
parts << country

parts.join(', ')
end

def street_address
return unless street
return street unless house_number

"#{house_number} #{street}"
end

def house_number
properties['housenumber']
end

def street
properties['street']
end

def postal_code
properties['postcode']
end

def city
properties['city']
end

def state
properties['state']
end

def state_code
''
end

def country
properties['country']
end

def country_code
''
end

def coordinates
return unless geometry
return unless geometry[:coordinates]

geometry[:coordinates].reverse
end

def geometry
return unless data['geometry']

symbol_hash data['geometry']
end

def bounds
properties['extent']
end

# Type of the result (OSM object type), one of:
#
# :node
# :way
# :relation
#
def type
{
'N' => :node,
'W' => :way,
'R' => :relation
}[properties['osm_type']]
end

def osm_id
properties['osm_id']
end

# See: https://wiki.openstreetmap.org/wiki/Tags
def osm_tag
return unless properties['osm_key']
return properties['osm_key'] unless properties['osm_value']

"#{properties['osm_key']}=#{properties['osm_value']}"
end

private

def properties
@properties ||= data['properties'] || {}
end

def symbol_hash(orig_hash)
{}.tap do |result|
orig_hash.each_key do |key|
next unless orig_hash[key]

result[key.to_sym] = orig_hash[key]
end
end
end
end
end
end
3 changes: 3 additions & 0 deletions test/fixtures/photon_invalid_request
@@ -0,0 +1,3 @@
{
"message":"some error happened and the response code is always 400"
}
34 changes: 34 additions & 0 deletions test/fixtures/photon_madison_square_garden
@@ -0,0 +1,34 @@
{
"features":[
{
"geometry":{
"coordinates":[
-73.99355027800776,
40.7505247
],
"type":"Point"
},
"type":"Feature",
"properties":{
"osm_id":138141251,
"osm_type":"W",
"extent":[
-73.9944446,
40.751161,
-73.9925924,
40.7498531
],
"country":"United States of America",
"osm_key":"leisure",
"housenumber":"4",
"city":"New York",
"street":"Pennsylvania Plaza",
"osm_value":"stadium",
"postcode":"10001",
"name":"Madison Square Garden",
"state":"New York"
}
}
],
"type":"FeatureCollection"
}
4 changes: 4 additions & 0 deletions test/fixtures/photon_no_results
@@ -0,0 +1,4 @@
{
"type":"FeatureCollection",
"features":[]
}
27 changes: 27 additions & 0 deletions test/fixtures/photon_reverse
@@ -0,0 +1,27 @@
{
"features":[
{
"geometry":{
"coordinates":[
-73.9935078,
40.750499
],
"type":"Point"
},
"type":"Feature",
"properties":{
"osm_id":6985936386,
"osm_type":"N",
"country":"United States of America",
"osm_key":"tourism",
"housenumber":"4",
"city":"New York",
"street":"Pennsylvania Plaza",
"osm_value":"attraction",
"postcode":"10121",
"state":"New York"
}
}
],
"type":"FeatureCollection"
}
16 changes: 16 additions & 0 deletions test/test_helper.rb
Expand Up @@ -554,6 +554,22 @@ def read_fixture(file)
MockHttpResponse.new(options)
end
end

require 'geocoder/lookups/photon'
class Photon
private
def read_fixture(file)
filepath = File.join("test", "fixtures", file)
s = File.read(filepath).strip.gsub(/\n\s*/, "")

options = { body: s, code: 200 }
if file == "photon_invalid_request"
options[:code] = 400
end

MockHttpResponse.new(options)
end
end
end
end

Expand Down

0 comments on commit f652a1a

Please sign in to comment.