aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Somerville <matthew@mysociety.org>2012-07-13 15:12:06 +0100
committerMatthew Somerville <matthew@mysociety.org>2012-07-13 15:12:30 +0100
commit0000afc1f4b28c96365981fc24437a6983ee7ea2 (patch)
tree4655b42784f1906b60acd8bce3af6c8d68febc42
parentdbeb763d227b011b42ccd44a8cb72a01dcea24ca (diff)
Add Nominatim-based geocoding service (fixes #183).
-rw-r--r--bin/generate_council_location2
-rw-r--r--notes/customisation.pod2
-rw-r--r--perllib/FixMyStreet/Cobrand/Barnet.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/Bromley.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/LichfieldDC.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/Reading.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/Southampton.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/UK.pm3
-rw-r--r--perllib/FixMyStreet/Geocode.pm7
-rw-r--r--perllib/FixMyStreet/Geocode/Bing.pm2
-rw-r--r--perllib/FixMyStreet/Geocode/Google.pm12
-rw-r--r--perllib/FixMyStreet/Geocode/OSM.pm69
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',