aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Somerville <matthew@balti.ukcod.org.uk>2010-11-22 18:28:20 +0000
committerMatthew Somerville <matthew@balti.ukcod.org.uk>2010-11-23 16:54:56 +0000
commit191acb0664c2edb5c60887caffb4bbc299317df1 (patch)
treea33dc563550c70241eb9c89c64a3ab359d5fd0b9
parentaaf1099a48feb5e7d6a76c0560c6be12ee4da9df (diff)
Factor out geocoding functions.
-rw-r--r--perllib/FixMyStreet/Geocode.pm168
-rw-r--r--perllib/Page.pm152
-rwxr-xr-xt/Page.t5
-rwxr-xr-xweb/alert.cgi5
-rwxr-xr-xweb/index.cgi11
-rwxr-xr-xweb/rss.cgi3
6 files changed, 184 insertions, 160 deletions
diff --git a/perllib/FixMyStreet/Geocode.pm b/perllib/FixMyStreet/Geocode.pm
new file mode 100644
index 000000000..04e440cc6
--- /dev/null
+++ b/perllib/FixMyStreet/Geocode.pm
@@ -0,0 +1,168 @@
+#!/usr/bin/perl
+#
+# FixMyStreet::Geocode
+# The geocoding functions for FixMyStreet.
+#
+# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved.
+# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/
+
+package FixMyStreet::Geocode;
+
+use strict;
+use Error qw(:try);
+use File::Slurp;
+use LWP::Simple;
+use Digest::MD5 qw(md5_hex);
+use URI::Escape;
+
+use Cobrand;
+use Page;
+use mySociety::Config;
+use mySociety::GeoUtil;
+use mySociety::MaPit;
+use mySociety::PostcodeUtil;
+use mySociety::Web qw(NewURL);
+
+BEGIN {
+ (my $dir = __FILE__) =~ s{/[^/]*?$}{};
+ mySociety::Config::set_file("$dir/../../conf/general");
+}
+
+# lookup STRING QUERY
+# Given a user-inputted string, try and convert it into co-ordinates using either
+# MaPit if it's a postcode, or Google Maps API otherwise. Returns an array of
+# data, including an error if there is one (which includes a location being in
+# Northern Ireland). The information in the query may be used by cobranded versions
+# of the site to diambiguate locations.
+sub lookup {
+ my ($s, $q) = @_;
+ my ($x, $y, $easting, $northing, $error);
+ if ($s =~ /^\d+$/) {
+ $error = 'FixMyStreet is a UK-based website that currently works in England, Scotland, and Wales. Please enter either a postcode, or a Great British street name and area.';
+ } elsif (mySociety::PostcodeUtil::is_valid_postcode($s)) {
+ my $location = mySociety::MaPit::call('postcode', $s);
+ unless ($error = Page::mapit_check_error($location)) {
+ $easting = $location->{easting};
+ $northing = $location->{northing};
+ my $xx = FixMyStreet::Map::os_to_tile($easting);
+ my $yy = FixMyStreet::Map::os_to_tile($northing);
+ $x = int($xx);
+ $y = int($yy);
+ $x += 1 if ($xx - $x > 0.5);
+ $y += 1 if ($yy - $y > 0.5);
+ }
+ } else {
+ ($x, $y, $easting, $northing, $error) = FixMyStreet::Geocode::string($s, $q);
+ }
+ return ($x, $y, $easting, $northing, $error);
+}
+
+sub geocoded_string_coordinates {
+ my ($js, $q) = @_;
+ my ($x, $y, $easting, $northing, $error);
+ my ($accuracy) = $js =~ /"Accuracy" *: *(\d)/;
+ if ($accuracy < 4) {
+ $error = _('Sorry, that location appears to be too general; please be more specific.');
+ } else {
+
+ $js =~ /"coordinates" *: *\[ *(.*?), *(.*?),/;
+ my $lon = $1; my $lat = $2;
+ try {
+ ($easting, $northing) = mySociety::GeoUtil::wgs84_to_national_grid($lat, $lon, 'G');
+ my $xx = FixMyStreet::Map::os_to_tile($easting);
+ my $yy = FixMyStreet::Map::os_to_tile($northing);
+ $x = int($xx);
+ $y = int($yy);
+ $x += 1 if ($xx - $x > 0.5);
+ $y += 1 if ($yy - $y > 0.5);
+ } catch Error::Simple with {
+ $error = shift;
+ $error = _('That location does not appear to be in Britain; please try again.')
+ if $error =~ /out of the area covered/;
+ }
+ }
+ return ($x, $y, $easting, $northing, $error);
+}
+
+# string STRING QUERY
+# Canonicalises, looks up on Google Maps API, and caches, a user-inputted location.
+# Returns array of (TILE_X, TILE_Y, EASTING, NORTHING, 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, $q) = @_;
+ $s = lc($s);
+ $s =~ s/[^-&0-9a-z ']/ /g;
+ $s =~ s/\s+/ /g;
+ $s = URI::Escape::uri_escape_utf8($s);
+ $s = Cobrand::disambiguate_location(Page::get_cobrand($q), "q=$s", $q);
+ $s =~ s/%20/+/g;
+ my $url = 'http://maps.google.com/maps/geo?' . $s;
+ my $cache_dir = mySociety::Config::get('GEO_CACHE');
+ my $cache_file = $cache_dir . md5_hex($url);
+ my ($js, $error, $x, $y, $easting, $northing);
+ if (-s $cache_file) {
+ $js = File::Slurp::read_file($cache_file);
+ } else {
+ $url .= ',+UK' unless $url =~ /united\++kingdom$/ || $url =~ /uk$/i;
+ $url .= '&sensor=false&gl=uk&key=' . mySociety::Config::get('GOOGLE_MAPS_API_KEY');
+ $js = LWP::Simple::get($url);
+ File::Slurp::write_file($cache_file, $js) if $js && $js !~ /"code":6[12]0/;
+ }
+ if (!$js) {
+ $error = _('Sorry, we could not parse that location. Please try again.');
+ } elsif ($js !~ /"code" *: *200/) {
+ $error = _('Sorry, we could not find that location.');
+ } elsif ($js =~ /}, *{/) { # Multiple
+ my @js = split /}, *{/, $js;
+ my @valid_locations;
+ foreach (@js) {
+ next unless /"address" *: *"(.*?)"/s;
+ my $address = $1;
+ next unless Cobrand::geocoded_string_check(Page::get_cobrand($q), $address, $q);
+ next if $address =~ /BT\d/;
+ push (@valid_locations, $_);
+ push (@$error, $address);
+ }
+ if (scalar @valid_locations == 1) {
+ return geocoded_string_coordinates($valid_locations[0], $q);
+ }
+ $error = _('Sorry, we could not find that location.') unless $error;
+ } elsif ($js =~ /BT\d/) {
+ # Northern Ireland, hopefully
+ $error = _("We do not cover Northern Ireland, I'm afraid, as our licence doesn't include any maps for the region.");
+ } else {
+ ($x, $y, $easting, $northing, $error) = geocoded_string_coordinates($js, $q);
+ }
+ return ($x, $y, $easting, $northing, $error);
+}
+
+# list_choices
+# Prints response if there's more than one possible result
+sub list_choices {
+ my ($choices, $page, $q) = @_;
+ my $url;
+ my $cobrand = Page::get_cobrand($q);
+ my $message = _('We found more than one match for that location. We show up to ten matches, please try a different search if yours is not here.');
+ my $out = '<p>' . $message . '</p>';
+ my $choice_list = '<ul>';
+ foreach my $choice (@$choices) {
+ $choice =~ s/, United Kingdom//;
+ $choice =~ s/, UK//;
+ $url = Cobrand::url($cobrand, NewURL($q, -retain => 1, -url => $page, 'pc' => $choice), $q);
+ $url =~ s/%20/+/g;
+ $choice_list .= '<li><a href="' . $url . '">' . $choice . "</a></li>\n";
+ }
+ $choice_list .= '</ul>';
+ $out .= $choice_list;
+ my %vars = (message => $message,
+ choice_list => $choice_list,
+ header => _('More than one match'),
+ url_home => Cobrand::url($cobrand, '/', $q));
+ my $cobrand_choice = Page::template_include('geocode-choice', $q, Page::template_root($q), %vars);
+ return $cobrand_choice if $cobrand_choice;
+ return $out;
+}
+
+1;
diff --git a/perllib/Page.pm b/perllib/Page.pm
index 06bdb27a4..b3ab35b33 100644
--- a/perllib/Page.pm
+++ b/perllib/Page.pm
@@ -16,42 +16,31 @@ use Carp;
use mySociety::CGIFast qw(-no_xhtml);
use Error qw(:try);
use File::Slurp;
-use HTTP::Date;
+use HTTP::Date; # time2str
use Image::Magick;
use Image::Size;
-use LWP::Simple;
-use Digest::MD5 qw(md5_hex);
use POSIX qw(strftime);
use URI::Escape;
use Text::Template;
use Memcached;
use Problems;
-use Utils;
use Cobrand;
use mySociety::Config;
use mySociety::DBHandle qw/dbh select_all/;
use mySociety::EvEl;
-use mySociety::Gaze;
-use mySociety::GeoUtil;
use mySociety::Locale;
use mySociety::MaPit;
-use mySociety::PostcodeUtil;
use mySociety::TempFiles;
use mySociety::Tracking;
use mySociety::WatchUpdate;
-use mySociety::Web qw(ent NewURL);
+use mySociety::Web qw(ent);
BEGIN {
(my $dir = __FILE__) =~ s{/[^/]*?$}{};
mySociety::Config::set_file("$dir/../conf/general");
}
-use constant TILE_WIDTH => mySociety::Config::get('TILES_WIDTH');
-use constant TIF_SIZE_M => mySociety::Config::get('TILES_TIFF_SIZE_METRES');
-use constant TIF_SIZE_PX => mySociety::Config::get('TILES_TIFF_SIZE_PIXELS');
-use constant SCALE_FACTOR => TIF_SIZE_M / (TIF_SIZE_PX / TILE_WIDTH);
-
my $lastmodified;
sub do_fastcgi {
@@ -658,143 +647,6 @@ sub mapit_check_error {
return 0;
}
-# geocode STRING QUERY
-# Given a user-inputted string, try and convert it into co-ordinates using either
-# MaPit if it's a postcode, or Google Maps API otherwise. Returns an array of
-# data, including an error if there is one (which includes a location being in
-# Northern Ireland). The information in the query may be used by cobranded versions
-# of the site to diambiguate locations.
-sub geocode {
- my ($s, $q) = @_;
- my ($x, $y, $easting, $northing, $error);
- if ($s =~ /^\d+$/) {
- $error = 'FixMyStreet is a UK-based website that currently works in England, Scotland, and Wales. Please enter either a postcode, or a Great British street name and area.';
- } elsif (mySociety::PostcodeUtil::is_valid_postcode($s)) {
- my $location = mySociety::MaPit::call('postcode', $s);
- unless ($error = mapit_check_error($location)) {
- $easting = $location->{easting};
- $northing = $location->{northing};
- my $xx = FixMyStreet::Map::os_to_tile($easting);
- my $yy = FixMyStreet::Map::os_to_tile($northing);
- $x = int($xx);
- $y = int($yy);
- $x += 1 if ($xx - $x > 0.5);
- $y += 1 if ($yy - $y > 0.5);
- }
- } else {
- ($x, $y, $easting, $northing, $error) = geocode_string($s, $q);
- }
- return ($x, $y, $easting, $northing, $error);
-}
-
-sub geocoded_string_coordinates {
- my ($js, $q) = @_;
- my ($x, $y, $easting, $northing, $error);
- my ($accuracy) = $js =~ /"Accuracy" *: *(\d)/;
- if ($accuracy < 4) {
- $error = _('Sorry, that location appears to be too general; please be more specific.');
- } else {
-
- $js =~ /"coordinates" *: *\[ *(.*?), *(.*?),/;
- my $lon = $1; my $lat = $2;
- try {
- ($easting, $northing) = mySociety::GeoUtil::wgs84_to_national_grid($lat, $lon, 'G');
- my $xx = FixMyStreet::Map::os_to_tile($easting);
- my $yy = FixMyStreet::Map::os_to_tile($northing);
- $x = int($xx);
- $y = int($yy);
- $x += 1 if ($xx - $x > 0.5);
- $y += 1 if ($yy - $y > 0.5);
- } catch Error::Simple with {
- $error = shift;
- $error = _('That location does not appear to be in Britain; please try again.')
- if $error =~ /out of the area covered/;
- }
- }
- return ($x, $y, $easting, $northing, $error);
-}
-
-# geocode_string STRING QUERY
-# Canonicalises, looks up on Google Maps API, and caches, a user-inputted location.
-# Returns array of (TILE_X, TILE_Y, EASTING, NORTHING, 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 geocode_string {
- my ($s, $q) = @_;
- $s = lc($s);
- $s =~ s/[^-&0-9a-z ']/ /g;
- $s =~ s/\s+/ /g;
- $s = URI::Escape::uri_escape_utf8($s);
- $s = Cobrand::disambiguate_location(get_cobrand($q), "q=$s", $q);
- $s =~ s/%20/+/g;
- my $url = 'http://maps.google.com/maps/geo?' . $s;
- my $cache_dir = mySociety::Config::get('GEO_CACHE');
- my $cache_file = $cache_dir . md5_hex($url);
- my ($js, $error, $x, $y, $easting, $northing);
- if (-s $cache_file) {
- $js = File::Slurp::read_file($cache_file);
- } else {
- $url .= ',+UK' unless $url =~ /united\++kingdom$/ || $url =~ /uk$/i;
- $url .= '&sensor=false&gl=uk&key=' . mySociety::Config::get('GOOGLE_MAPS_API_KEY');
- $js = LWP::Simple::get($url);
- File::Slurp::write_file($cache_file, $js) if $js && $js !~ /"code":6[12]0/;
- }
- if (!$js) {
- $error = _('Sorry, we could not parse that location. Please try again.');
- } elsif ($js !~ /"code" *: *200/) {
- $error = _('Sorry, we could not find that location.');
- } elsif ($js =~ /}, *{/) { # Multiple
- my @js = split /}, *{/, $js;
- my @valid_locations;
- foreach (@js) {
- next unless /"address" *: *"(.*?)"/s;
- my $address = $1;
- next unless Cobrand::geocoded_string_check(get_cobrand($q), $address, $q);
- next if $address =~ /BT\d/;
- push (@valid_locations, $_);
- push (@$error, $address);
- }
- if (scalar @valid_locations == 1) {
- return geocoded_string_coordinates($valid_locations[0], $q);
- }
- $error = _('Sorry, we could not find that location.') unless $error;
- } elsif ($js =~ /BT\d/) {
- # Northern Ireland, hopefully
- $error = _("We do not cover Northern Ireland, I'm afraid, as our licence doesn't include any maps for the region.");
- } else {
- ($x, $y, $easting, $northing, $error) = geocoded_string_coordinates($js, $q);
- }
- return ($x, $y, $easting, $northing, $error);
-}
-
-# geocode_choice
-# Prints response if there's more than one possible result
-sub geocode_choice {
- my ($choices, $page, $q) = @_;
- my $url;
- my $cobrand = Page::get_cobrand($q);
- my $message = _('We found more than one match for that location. We show up to ten matches, please try a different search if yours is not here.');
- my $out = '<p>' . $message . '</p>';
- my $choice_list = '<ul>';
- foreach my $choice (@$choices) {
- $choice =~ s/, United Kingdom//;
- $choice =~ s/, UK//;
- $url = Cobrand::url($cobrand, NewURL($q, -retain => 1, -url => $page, 'pc' => $choice), $q);
- $url =~ s/%20/+/g;
- $choice_list .= '<li><a href="' . $url . '">' . $choice . "</a></li>\n";
- }
- $choice_list .= '</ul>';
- $out .= $choice_list;
- my %vars = (message => $message,
- choice_list => $choice_list,
- header => _('More than one match'),
- url_home => Cobrand::url($cobrand, '/', $q));
- my $cobrand_choice = Page::template_include('geocode-choice', $q, Page::template_root($q), %vars);
- return $cobrand_choice if $cobrand_choice;
- return $out;
-}
-
sub short_name {
my $name = shift;
# Special case Durham as it's the only place with two councils of the same name
diff --git a/t/Page.t b/t/Page.t
index 26962c743..831986f80 100755
--- a/t/Page.t
+++ b/t/Page.t
@@ -20,6 +20,7 @@ use lib "$FindBin::Bin/../perllib";
use lib "$FindBin::Bin/../commonlib/perllib";
use Page;
+use FixMyStreet::Geocode;
use mySociety::MockQuery;
use mySociety::Locale;
@@ -40,14 +41,14 @@ sub test_geocode_string() {
my $q = new MockQuery('nosite', \%params);
# geocode a straightforward string, expect success
- my ($x, $y, $easting, $northing, $error) = Page::geocode_string('Buckingham Palace', $q);
+ my ($x, $y, $easting, $northing, $error) = FixMyStreet::Geocode::string('Buckingham Palace', $q);
ok($x == 3280, 'example x coordinate generated') or diag("Got $x");
ok($y == 1114, 'example y coordinate generated') or diag("Got $y");;
ok($easting == 529044, 'example easting generated') or diag("Got $easting");
ok($northing == 179619, 'example northing generated') or diag("Got $northing");
ok(! defined($error), 'should not generate error for simple example') or diag("Got $error");
# expect a failure message for Northern Ireland
- ($x, $y, $easting, $northing, $error) = Page::geocode_string('Falls Road, Belfast', $q);
+ ($x, $y, $easting, $northing, $error) = FixMyStreet::Geocode::string('Falls Road, Belfast', $q);
ok($error eq "We do not cover Northern Ireland, I'm afraid, as our licence doesn't include any maps for the region.", 'error message produced for NI location') or diag("Got $error");
}
diff --git a/web/alert.cgi b/web/alert.cgi
index 46eef082b..70b9d1873 100755
--- a/web/alert.cgi
+++ b/web/alert.cgi
@@ -14,6 +14,7 @@ use Digest::SHA1 qw(sha1_hex);
use Error qw(:try);
use CrossSell;
use FixMyStreet::Alert;
+use FixMyStreet::Geocode;
use mySociety::AuthToken;
use mySociety::Config;
use mySociety::DBHandle qw(select_all);
@@ -84,12 +85,12 @@ sub alert_list {
$n = FixMyStreet::Map::tile_to_os($input{y});
} else {
try {
- ($x, $y, $e, $n, $error) = Page::geocode($input{pc}, $q);
+ ($x, $y, $e, $n, $error) = FixMyStreet::Geocode::lookup($input{pc}, $q);
} catch Error::Simple with {
$error = shift;
};
}
- return Page::geocode_choice($error, '/alert', $q) if ref($error) eq 'ARRAY';
+ return FixMyStreet::Geocode::list_choices($error, '/alert', $q) if ref($error) eq 'ARRAY';
return alert_front_page($q, $error) if $error;
my $pretty_pc = $input_h{pc};
diff --git a/web/index.cgi b/web/index.cgi
index 00778c442..1ffce4660 100755
--- a/web/index.cgi
+++ b/web/index.cgi
@@ -16,6 +16,7 @@ use CGI::Carp;
use URI::Escape;
use CrossSell;
+use FixMyStreet::Geocode;
use mySociety::AuthToken;
use mySociety::Config;
use mySociety::DBHandle qw(select_all);
@@ -469,7 +470,7 @@ sub display_form {
$easting = FixMyStreet::Map::tile_to_os($input{x});
$northing = FixMyStreet::Map::tile_to_os($input{y});
} else {
- my ($x, $y, $e, $n, $error) = Page::geocode($input{pc}, $q);
+ my ($x, $y, $e, $n, $error) = FixMyStreet::Geocode::lookup($input{pc}, $q);
$easting = $e; $northing = $n;
}
} elsif ($pin_x && $pin_y) {
@@ -485,11 +486,11 @@ sub display_form {
} elsif ($input{partial} && $input{pc} && !$input{easting} && !$input{northing}) {
my ($x, $y, $error);
try {
- ($x, $y, $easting, $northing, $error) = Page::geocode($input{pc}, $q);
+ ($x, $y, $easting, $northing, $error) = FixMyStreet::Geocode::lookup($input{pc}, $q);
} catch Error::Simple with {
$error = shift;
};
- return Page::geocode_choice($error, '/', $q) if ref($error) eq 'ARRAY';
+ return FixMyStreet::Geocode::list_choices($error, '/', $q) if ref($error) eq 'ARRAY';
return front_page($q, $error) if $error;
$input{x} = int(FixMyStreet::Map::os_to_tile($easting));
$input{y} = int(FixMyStreet::Map::os_to_tile($northing));
@@ -794,12 +795,12 @@ sub display_location {
return front_page($q, @errors) unless $x || $y || $input{pc};
if (!$x && !$y) {
try {
- ($x, $y, $easting, $northing, $error) = Page::geocode($input{pc}, $q);
+ ($x, $y, $easting, $northing, $error) = FixMyStreet::Geocode::lookup($input{pc}, $q);
} catch Error::Simple with {
$error = shift;
};
}
- return Page::geocode_choice($error, '/', $q) if (ref($error) eq 'ARRAY');
+ return FixMyStreet::Geocode::list_choices($error, '/', $q) if (ref($error) eq 'ARRAY');
return front_page($q, $error) if ($error);
if (!$easting || !$northing) {
diff --git a/web/rss.cgi b/web/rss.cgi
index effa15f08..623314e7f 100755
--- a/web/rss.cgi
+++ b/web/rss.cgi
@@ -13,6 +13,7 @@ use Error qw(:try);
use Standard;
use URI::Escape;
use FixMyStreet::Alert;
+use FixMyStreet::Geocode;
use mySociety::MaPit;
use mySociety::GeoUtil;
use mySociety::Gaze;
@@ -93,7 +94,7 @@ sub rss_local_problems {
} elsif ($pc) {
my $error;
try {
- ($x, $y, $e, $n, $error) = Page::geocode($pc, $q);
+ ($x, $y, $e, $n, $error) = FixMyStreet::Geocode::lookup($pc, $q);
} catch Error::Simple with {
$error = shift;
};