diff options
author | Matthew Somerville <matthew@mysociety.org> | 2012-07-13 15:12:06 +0100 |
---|---|---|
committer | Matthew Somerville <matthew@mysociety.org> | 2012-07-13 15:12:30 +0100 |
commit | 0000afc1f4b28c96365981fc24437a6983ee7ea2 (patch) | |
tree | 4655b42784f1906b60acd8bce3af6c8d68febc42 | |
parent | dbeb763d227b011b42ccd44a8cb72a01dcea24ca (diff) |
Add Nominatim-based geocoding service (fixes #183).
-rw-r--r-- | bin/generate_council_location | 2 | ||||
-rw-r--r-- | notes/customisation.pod | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Barnet.pm | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Bromley.pm | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/LichfieldDC.pm | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Reading.pm | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Southampton.pm | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/UK.pm | 3 | ||||
-rw-r--r-- | perllib/FixMyStreet/Geocode.pm | 7 | ||||
-rw-r--r-- | perllib/FixMyStreet/Geocode/Bing.pm | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/Geocode/Google.pm | 12 | ||||
-rw-r--r-- | perllib/FixMyStreet/Geocode/OSM.pm | 69 |
12 files changed, 87 insertions, 20 deletions
diff --git a/bin/generate_council_location b/bin/generate_council_location index 36bf14d04..6acb21b0e 100644 --- a/bin/generate_council_location +++ b/bin/generate_council_location @@ -22,5 +22,5 @@ my $spn_lon = $max_lon - $min_lon; print <<"EOT"; centre => '$c_lat,$c_lon', span => '$spn_lat,$spn_lon', - bounds => [ '$min_lat,$min_lon','$max_lat,$max_lon' ], + bounds => [ $min_lat, $min_lon, $max_lat, $max_lon ], EOT diff --git a/notes/customisation.pod b/notes/customisation.pod index 04bfb6e1d..fedd9da44 100644 --- a/notes/customisation.pod +++ b/notes/customisation.pod @@ -133,7 +133,7 @@ that the geocoder will consider: return { centre => '52.688198,-1.804966', span => '0.1196,0.218675', - bounds => [ '52.807793,-1.586291', '52.584891,-1.963232' ], + bounds => [ 52.807793, -1.586291, 52.584891, -1.963232 ], }; } diff --git a/perllib/FixMyStreet/Cobrand/Barnet.pm b/perllib/FixMyStreet/Cobrand/Barnet.pm index d33204521..7320edc40 100644 --- a/perllib/FixMyStreet/Cobrand/Barnet.pm +++ b/perllib/FixMyStreet/Cobrand/Barnet.pm @@ -31,7 +31,7 @@ sub disambiguate_location { town => 'Barnet', centre => '51.612832,-0.218169', span => '0.0563,0.09', - bounds => [ '51.584682,-0.263169', '51.640982,-0.173169' ], + bounds => [ 51.584682, -0.263169, 51.640982, -0.173169 ], }; } diff --git a/perllib/FixMyStreet/Cobrand/Bromley.pm b/perllib/FixMyStreet/Cobrand/Bromley.pm index c5c6c6345..a52875819 100644 --- a/perllib/FixMyStreet/Cobrand/Bromley.pm +++ b/perllib/FixMyStreet/Cobrand/Bromley.pm @@ -42,7 +42,7 @@ sub disambiguate_location { town => 'Bromley', centre => '51.366836,0.040623', span => '0.154963,0.24347', - bounds => [ '51.289355,-0.081112', '51.444318,0.162358' ], + bounds => [ 51.289355, -0.081112, 51.444318, 0.162358 ], }; } diff --git a/perllib/FixMyStreet/Cobrand/LichfieldDC.pm b/perllib/FixMyStreet/Cobrand/LichfieldDC.pm index f98775c91..fb00aec48 100644 --- a/perllib/FixMyStreet/Cobrand/LichfieldDC.pm +++ b/perllib/FixMyStreet/Cobrand/LichfieldDC.pm @@ -22,7 +22,7 @@ sub disambiguate_location { %{ $self->SUPER::disambiguate_location() }, centre => '52.688198,-1.804966', span => '0.1196,0.218675', - bounds => [ '52.584891,-1.963232', '52.807793,-1.586291' ], + bounds => [ 52.584891, -1.963232, 52.807793, -1.586291 ], }; } diff --git a/perllib/FixMyStreet/Cobrand/Reading.pm b/perllib/FixMyStreet/Cobrand/Reading.pm index c8591924e..4cd3f82e2 100644 --- a/perllib/FixMyStreet/Cobrand/Reading.pm +++ b/perllib/FixMyStreet/Cobrand/Reading.pm @@ -18,7 +18,7 @@ sub disambiguate_location { town => 'Reading', centre => '51.452983,-0.983827', span => '0.083355,0.1245', - bounds => [ '51.409779,-1.052994', '51.493134,-0.928494' ], + bounds => [ 51.409779, -1.052994, 51.493134, -0.928494 ], }; } diff --git a/perllib/FixMyStreet/Cobrand/Southampton.pm b/perllib/FixMyStreet/Cobrand/Southampton.pm index b57091bef..b7374149a 100644 --- a/perllib/FixMyStreet/Cobrand/Southampton.pm +++ b/perllib/FixMyStreet/Cobrand/Southampton.pm @@ -16,7 +16,7 @@ sub disambiguate_location { town => 'Southampton', centre => '50.913822,-1.400493', span => '0.084628,0.15701', - bounds => [ '50.871508,-1.478998', '50.956136,-1.321988' ], + bounds => [ 50.871508, -1.478998, 50.956136, -1.321988 ], }; } diff --git a/perllib/FixMyStreet/Cobrand/UK.pm b/perllib/FixMyStreet/Cobrand/UK.pm index f160d193e..093ba152b 100644 --- a/perllib/FixMyStreet/Cobrand/UK.pm +++ b/perllib/FixMyStreet/Cobrand/UK.pm @@ -10,7 +10,8 @@ sub area_min_generation { 10 } sub disambiguate_location { return { - country => 'uk', + country => 'gb', + google_country => 'uk', bing_culture => 'en-GB', bing_country => 'United Kingdom' }; diff --git a/perllib/FixMyStreet/Geocode.pm b/perllib/FixMyStreet/Geocode.pm index 6ee17029a..e5983810b 100644 --- a/perllib/FixMyStreet/Geocode.pm +++ b/perllib/FixMyStreet/Geocode.pm @@ -12,6 +12,7 @@ use strict; use URI::Escape; use FixMyStreet::Geocode::Bing; use FixMyStreet::Geocode::Google; +use FixMyStreet::Geocode::OSM; # lookup STRING CONTEXT # Given a user-inputted string, try and convert it into co-ordinates using either @@ -41,8 +42,10 @@ sub string { my $params = $c->cobrand->disambiguate_location(); return FixMyStreet::Geocode::Bing::string($s, $c, $params) if FixMyStreet->config('BING_MAPS_API_KEY'); - # Fall back to Google API, which allow acces with and without a key - return FixMyStreet::Geocode::Google::string($s, $c, $params); + # Fall back to Google API, which allow access with and without a key + return FixMyStreet::Geocode::Google::string($s, $c, $params) + if FixMyStreet->config('GOOGLE_MAPS_API_KEY'); + return FixMyStreet::Geocode::OSM::string($s, $c, $params); } 1; diff --git a/perllib/FixMyStreet/Geocode/Bing.pm b/perllib/FixMyStreet/Geocode/Bing.pm index 148ad5f43..13542347d 100644 --- a/perllib/FixMyStreet/Geocode/Bing.pm +++ b/perllib/FixMyStreet/Geocode/Bing.pm @@ -24,7 +24,7 @@ sub string { my ( $s, $c, $params ) = @_; $s .= '+' . $params->{town} if $params->{town} and $s !~ /$params->{town}/i; my $url = "http://dev.virtualearth.net/REST/v1/Locations?q=$s"; - $url .= '&userMapView=' . $params->{bounds}[0] . ',' . $params->{bounds}[1] + $url .= '&userMapView=' . join(',', @{$params->{bounds}}) if $params->{bounds}; $url .= '&userLocation=' . $params->{centre} if $params->{centre}; $url .= '&c=' . $params->{bing_culture} if $params->{bing_culture}; diff --git a/perllib/FixMyStreet/Geocode/Google.pm b/perllib/FixMyStreet/Geocode/Google.pm index 1ab347066..0d568f8f1 100644 --- a/perllib/FixMyStreet/Geocode/Google.pm +++ b/perllib/FixMyStreet/Geocode/Google.pm @@ -24,10 +24,14 @@ sub string { my ( $s, $c, $params ) = @_; my $url = 'http://maps.google.com/maps/geo?q=' . $s; - $url .= '&ll=' . $params->{centre} if $params->{centre}; - $url .= '&spn=' . $params->{span} if $params->{span}; - $url .= '&gl=' . $params->{country} if $params->{country}; - $url .= '&hl=' . $params->{lang} if $params->{lang}; + $url .= '&ll=' . $params->{centre} if $params->{centre}; + $url .= '&spn=' . $params->{span} if $params->{span}; + if ($params->{google_country}) { + $url .= '&gl=' . $params->{google_country}; + } elsif ($params->{country}) { + $url .= '&gl=' . $params->{country}; + } + $url .= '&hl=' . $params->{lang} if $params->{lang}; my $cache_dir = FixMyStreet->config('GEO_CACHE') . 'google/'; my $cache_file = $cache_dir . md5_hex($url); diff --git a/perllib/FixMyStreet/Geocode/OSM.pm b/perllib/FixMyStreet/Geocode/OSM.pm index b1becaa7a..ba939b443 100644 --- a/perllib/FixMyStreet/Geocode/OSM.pm +++ b/perllib/FixMyStreet/Geocode/OSM.pm @@ -11,20 +11,79 @@ package FixMyStreet::Geocode::OSM; use warnings; use strict; -use Memcached; -use mySociety::Config; +use Digest::MD5 qw(md5_hex); +use Encode; +use File::Slurp; +use File::Path (); use LWP::Simple; +use Memcached; use XML::Simple; my $osmapibase = "http://www.openstreetmap.org/api/"; my $nominatimbase = "http://nominatim.openstreetmap.org/"; +# string STRING CONTEXT +# Looks up on Nominatim, and caches, a user-inputted location. +# Returns array of (LAT, LON, ERROR), where ERROR is either undef, a string, or +# an array of matches if there are more than one. The information in the query +# may be used to disambiguate the location in cobranded versions of the site. +sub string { + my ( $s, $c, $params ) = @_; + $s .= '+' . $params->{town} if $params->{town} and $s !~ /$params->{town}/i; + my $url = "${nominatimbase}search?"; + my %query_params = ( + q => $s, + format => 'json', + #'accept-language' => '', + email => 'support' . chr(64) . 'fixmystreet.com', + ); + $query_params{viewbox} = $params->{bounds}[1] . ',' . $params->{bounds}[2] . ',' . $params->{bounds}[3] . ',' . $params->{bounds}[0] + if $params->{bounds}; + $query_params{countrycodes} = $params->{country} + if $params->{country}; + $url .= join('&', map { "$_=$query_params{$_}" } keys %query_params); + + my $cache_dir = FixMyStreet->config('GEO_CACHE') . 'osm/'; + my $cache_file = $cache_dir . md5_hex($url); + my $js; + if (-s $cache_file) { + $js = File::Slurp::read_file($cache_file); + } else { + $js = LWP::Simple::get($url); + $js = encode_utf8($js) if utf8::is_utf8($js); + File::Path::mkpath($cache_dir); + File::Slurp::write_file($cache_file, $js) if $js; + } + + if (!$js) { + return { error => _('Sorry, we could not parse that location. Please try again.') }; + } + + $js = JSON->new->utf8->allow_nonref->decode($js); + + my ( $error, @valid_locations, $latitude, $longitude ); + foreach (@$js) { + # These co-ordinates are output as query parameters in a URL, make sure they have a "." + ( $latitude, $longitude ) = ( $_->{lat}, $_->{lon} ); + mySociety::Locale::in_gb_locale { + push (@$error, { + address => $_->{display_name}, + latitude => sprintf('%0.6f', $latitude), + longitude => sprintf('%0.6f', $longitude) + }); + }; + push (@valid_locations, $_); + } + + return { latitude => $latitude, longitude => $longitude } if scalar @valid_locations == 1; + return { error => $error }; +} -sub lookup_location { +sub reverse_geocode { my ($latitude, $longitude, $zoom) = @_; my $url = "${nominatimbase}reverse?format=xml&zoom=$zoom&lat=$latitude&lon=$longitude"; - my $key = "OSM:lookup_location:$url"; + my $key = "OSM:reverse_geocode:$url"; my $result = Memcached::get($key); unless ($result) { my $j = LWP::Simple::get($url); @@ -74,7 +133,7 @@ sub get_object_tags { # http://www.geonames.org/maps/osm-reverse-geocoder.html#findNearbyStreetsOSM sub get_nearest_road_tags { my ( $cobrand, $latitude, $longitude ) = @_; - my $inforef = lookup_location($latitude, $longitude, 16); + my $inforef = reverse_geocode($latitude, $longitude, 16); if (exists $inforef->{result}->{osm_type} && 'way' eq $inforef->{result}->{osm_type}) { my $osmtags = get_object_tags('way', |