diff options
-rwxr-xr-x | bin/import-flickr | 14 | ||||
-rwxr-xr-x | bin/send-reports | 46 | ||||
-rwxr-xr-x | bin/test-run | 79 | ||||
-rwxr-xr-x | bin/update-areas | 4 | ||||
-rw-r--r-- | conf/general-example | 3 | ||||
-rw-r--r-- | conf/httpd.conf | 14 | ||||
-rw-r--r-- | db/migrate_from_osgb36_to_wgs84.pl | 201 | ||||
-rw-r--r-- | db/schema.sql | 40 | ||||
-rw-r--r-- | notes/INSTALL | 4 | ||||
-rw-r--r-- | notes/osgb36_to_wgs84_notes.txt | 77 | ||||
-rw-r--r-- | perllib/Carp/Always.pm | 162 | ||||
-rw-r--r-- | perllib/Cobrands/Barnet/Util.pm | 4 | ||||
-rw-r--r-- | perllib/FixMyStreet/Alert.pm | 14 | ||||
-rw-r--r-- | perllib/FixMyStreet/Geocode.pm | 36 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map.pm | 93 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/Tilma/Original.pm | 57 | ||||
-rw-r--r-- | perllib/Page.pm | 5 | ||||
-rw-r--r-- | perllib/Problems.pm | 30 | ||||
-rw-r--r-- | perllib/Utils.pm | 70 | ||||
-rwxr-xr-x | t/Page.t | 27 | ||||
-rw-r--r-- | t/utils.t | 41 | ||||
-rw-r--r-- | templates/emails/submit-brent | 4 | ||||
-rw-r--r-- | templates/emails/submit-council | 4 | ||||
-rwxr-xr-x | web-admin/index.cgi | 6 | ||||
-rwxr-xr-x | web/alert.cgi | 54 | ||||
-rwxr-xr-x | web/import.cgi | 31 | ||||
-rwxr-xr-x | web/index.cgi | 235 | ||||
-rwxr-xr-x | web/questionnaire.cgi | 4 | ||||
-rwxr-xr-x | web/rss.cgi | 33 |
29 files changed, 1099 insertions, 293 deletions
diff --git a/bin/import-flickr b/bin/import-flickr index 992a6ac8e..21ea5822a 100755 --- a/bin/import-flickr +++ b/bin/import-flickr @@ -72,19 +72,19 @@ while ($result =~ /<photo id="([^"]*)" owner="([^"]*)" secret="([^"]*)" server=" sub problem_create { my ($photo_id, $owner, $title, $lat, $lon, $image) = @_; my ($name, $email) = dbh()->selectrow_array("select name, email from partial_user where service='flickr' and nsid=?", {}, $owner); - my ($easting, $northing) = (0,0); + + # set some defaults $name ||= ''; - if ($lat && $lon) { - # XXX This appears to be going wrong :( - ($easting, $northing) = mySociety::GeoUtil::wgs84_to_national_grid($lat, $lon, 'G'); - } + $lat ||= 0; + $lon ||= 0; + my $id = dbh()->selectrow_array("select nextval('problem_id_seq')"); Utils::workaround_pg_bytea("insert into problem - (id, postcode, easting, northing, title, detail, name, + (id, postcode, latitude, longitude, title, detail, name, email, phone, photo, state, used_map, anonymous, category, areas) values (?, '', ?, ?, ?, '', ?, ?, '', ?, 'partial', 't', 'f', '', '')", 7, - $id, $easting, $northing, $title, $name, $email, $image + $id, $latitude, $longitude, $title, $name, $email, $image ); dbh()->do('insert into flickr_imported (id, problem_id) values (?, ?)', {}, $photo_id, $id); diff --git a/bin/send-reports b/bin/send-reports index 9da9b6238..6254337c6 100755 --- a/bin/send-reports +++ b/bin/send-reports @@ -27,6 +27,7 @@ use mySociety::Config; use mySociety::DBHandle qw(dbh); use mySociety::Email; use mySociety::EmailUtil; +use mySociety::GeoUtil; use mySociety::Locale; use mySociety::MaPit; use mySociety::Random qw(random_bytes); @@ -49,10 +50,12 @@ my $base_url = mySociety::Config::get('BASE_URL'); my $site = CronFns::site($base_url); my $query = "SELECT id, council, category, title, detail, name, email, phone, - used_map, easting, northing, (photo is not null) as has_photo, lang, + used_map, latitude, longitude, (photo is not null) as has_photo, lang, cobrand, cobrand_data - FROM problem WHERE state in ('confirmed','fixed') AND whensent IS NULL - AND council IS NOT NULL"; + FROM problem + WHERE state in ('confirmed','fixed') + AND whensent IS NULL + AND council IS NOT NULL"; my $unsent = dbh()->selectall_arrayref($query, { Slice => {} }); my (%notgot, %note); @@ -81,7 +84,7 @@ foreach my $row (@$unsent) { # Template variables for the email my $email_base_url = Cobrand::base_url_for_emails($cobrand, $cobrand_data); - my %h = map { $_ => $row->{$_} } qw/title detail name email phone category easting northing/; + my %h = map { $_ => $row->{$_} } qw/title detail name email phone category latitude longitude/; $h{url} = $email_base_url . '/report/' . $row->{id}; $h{phone_line} = $h{phone} ? _('Phone:') . " $h{phone}\n\n" : ''; if ($row->{has_photo}) { @@ -95,6 +98,21 @@ foreach my $row (@$unsent) { : _('The user could not locate the problem on a map, but to see the area around the location they entered'); $h{closest_address} = ''; $h{closest_address_machine} = ''; + + # If we are in the UK include eastings and northings + $h{easting_northing} = ''; + if ( mySociety::Config::get('COUNTRY') eq 'GB' ) { + + ( $h{easting}, $h{northing} ) = + mySociety::GeoUtil::wgs84_to_national_grid( # + $h{latitude}, $h{longitude}, 'G' + ); + + # email templates don't have conditionals so we need to farmat this here + $h{easting_northing} # + = "Easting: $h{easting}\n\n" # + . "Northing: $h{northing}\n\n"; + } my (@to, @recips, $template, $areas_info); if ($site eq 'emptyhomes') { @@ -137,8 +155,8 @@ foreach my $row (@$unsent) { my ($council_email, $confirmed, $note) = dbh()->selectrow_array( "SELECT email,confirmed,note FROM contacts WHERE deleted='f' and area_id=? AND category=?", {}, $council, $row->{category}); - $council_email = essex_contact($row->{easting}, $row->{northing}) if $council == 2225; - $council_email = oxfordshire_contact($row->{easting}, $row->{northing}) if $council == 2237 && $council_email eq 'SPECIAL'; + $council_email = essex_contact($row->{latitude}, $row->{longitude}) if $council == 2225; + $council_email = oxfordshire_contact($row->{latitude}, $row->{longitude}) if $council == 2237 && $council_email eq 'SPECIAL'; unless ($confirmed) { $all_confirmed = 0; $note = 'Council ' . $row->{council} . ' deleted' @@ -255,13 +273,19 @@ if ($verbose) { } } +sub _get_district_for_contact { + my ( $lat, $lon ) = @_; + my $district = + mySociety::MaPit::call( 'point', "4326/$lon,$lat", type => 'DIS' ); + ($district) = keys %$district; + return $district; +} + # Essex has different contact addresses depending upon the district # Might be easier if we start storing in the db all areas covered by a point # Will do for now :) sub essex_contact { - my ($E, $N) = @_; - my $district = mySociety::MaPit::call('point', "27700/$E,$N", type => 'DIS'); - ($district) = keys %$district; + my $district = _get_district_for_contact(@_); my $email; $email = 'eastarea' if $district == 2315 || $district == 2312; $email = 'midarea' if $district == 2317 || $district == 2314 || $district == 2316; @@ -273,9 +297,7 @@ sub essex_contact { # Oxfordshire has different contact addresses depending upon the district sub oxfordshire_contact { - my ($E, $N) = @_; - my $district = mySociety::MaPit::call('point', "27700/$E,$N", type => 'DIS'); - ($district) = keys %$district; + my $district = _get_district_for_contact(@_); my $email; $email = 'northernarea' if $district == 2419 || $district == 2420 || $district == 2421; $email = 'southernarea' if $district == 2417 || $district == 2418; diff --git a/bin/test-run b/bin/test-run index 15f9c3ed2..85ce87797 100755 --- a/bin/test-run +++ b/bin/test-run @@ -27,11 +27,14 @@ use FindBin; use mySociety::Config; mySociety::Config::set_file('../conf/general'); use mySociety::DBHandle qw(dbh); +use mySociety::GeoUtil; use mySociety::WebTestHarness; use Test::Harness; use File::Find; use lib "$FindBin::Bin/../perllib"; use Cobrand; +use FixMyStreet::Geocode; +use Utils; my @actions = ('report', 'update', 'questionnaire', 'alert', 'static', 'cobrand', 'unit', 'eha_alert', 'import', 'rss'); my %actions_desc = ( @@ -213,6 +216,11 @@ sub submit_postcode{ sub submit_report { my ($postcode, $x, $y, $easting, $northing, $user_num, $council, $texts, $cobrand ) = @_; my @messages = @{$texts}; + + # convert easting, northing to lat lon + ( $latitude, $longitude ) = + Utils::convert_en_to_latlon( $easting, $northing ); + submit_postcode($cobrand, $postcode, $messages[0]); { # Writing values to hidden fields, so switching @@ -235,8 +243,8 @@ sub submit_report { y => $y, pc => $postcode, council => -1, - easting => $easting, - northing => $northing, + latitude => $latitude, + longitude => $longitude, title => 'My test problem', detail => 'Detail of my test problem', anonymous => 1, @@ -377,13 +385,23 @@ sub do_alert { # sign up for alerts in an area my $postcode = 'EH1 2NG'; - my $x = 2015; my $e = 325066; - my $y = 4175; my $n = 673533; + + my $x = 2015; + my $y = 4175; + + my $e = 325066; + my $n = 673533; + + # get the lat,lon from the postcode so that it matches + my ( $lat, $lon ) = + map { Utils::truncate_coordinate($_) } + FixMyStreet::Geocode::lookup( $postcode, undef ); + my $messages = english_fms_messages(); submit_postcode('', $postcode, 'Problems in this area'); $wth->browser_follow_link(text => 'Email me new local problems'); $wth->browser_submit_form(form_name => 'alerts', - fields => {feed => "local:" . $e . ":" . $n, + fields => {feed => "local:$lat:$lon", rznvy => email_n(2)} ); $wth->browser_check_contents('Nearly Done!'); my $confirmation_email = $wth->email_get_containing( @@ -396,7 +414,7 @@ sub do_alert { $wth->browser_check_contents('successfully confirmed your alert'); # create and confirm a new problem in the area - submit_report($postcode, $x, $y, 325000, 673387.096774193, 3, undef, $messages, ''); + submit_report($postcode, $x, $y, $e, $n, 3, undef, $messages, ''); # run the alert script call_send_emails(); @@ -419,6 +437,12 @@ sub do_eha_alert { my $postcode = 'EH1 2NG'; my $x = 2015; my $e = 325066; my $y = 4175; my $n = 673533; + + # get the lat,lon from the postcode so that it matches + my ( $lat, $lon ) = + map { Utils::truncate_coordinate($_) } + FixMyStreet::Geocode::lookup( $postcode, undef ); + my @texts = ('Eiddo gwag yn yr ardal hon', 'Adrodd am eiddo gwag', 'Nawr, gwiriwch eich e-bost', @@ -428,7 +452,7 @@ sub do_eha_alert { submit_postcode('cy.emptyhomes.', $postcode, $texts[0]); $wth->browser_follow_link(text => 'Anfonwch fanylion eiddo gwag lleol newydd ataf i drwy\'r e-bost'); $wth->browser_submit_form(form_name => 'alerts', - fields => {feed => "local:" . $e . ":" . $n, + fields => {feed => "local:$lat:$lon", rznvy => email_n(4)} ); $wth->browser_check_contents($texts[2]); my $confirmation_email = $wth->email_get_containing( @@ -441,7 +465,7 @@ sub do_eha_alert { $wth->browser_check_contents('Rydych wedi cadarnhau\'ch hysbysiad yn llwyddiannus.'); # create and confirm a new problem in the area - submit_report($postcode, $x, $y, 325000, 673387.096774193, 3, undef, \@texts, 'cy.emptyhomes.'); + submit_report($postcode, $x, $y, $e, $n, 3, undef, \@texts, 'cy.emptyhomes.'); # run the alert script call_send_emails(); @@ -513,16 +537,37 @@ sub do_static { # Check RSS feeds redirect to the right places and so on. # Just checks header, doesn't check any contents. sub do_rss { - $wth->browser_get($base_url . '/rss/l/52.5/-1.9'); - die "Lat/lon redirect failed" unless $wth->browser_uri() =~ m{/rss/n/406886,289126$}; - $wth->browser_get($base_url . '/rss/2524/1779'); - die "Tile redirect failed" unless $wth->browser_uri() =~ m{/rss/n/407097,286935$}; - $wth->browser_get($base_url . '/rss/n/407097,286935'); + + my %redirects = ( + + # should always go to lat lon + '/rss/n/406886,289126' => '/rss/l/52.499993,-1.899993', + '/rss/2524/1779' => '/rss/l/52.480294,-1.896931', + '/rss/pc/SW1A1AA' => '/rss/l/51.501010,-0.141587', + '/rss/l/52.5/-1.9' => '/rss/l/52.5/-1.9', + + # go to reports + '/rss/area/Birmingham' => '/rss/reports/Birmingham', + '/rss/area/Birmingham/Lozells' => '/rss/reports/Birmingham/Lozells', + ); + + my $error_count = 0; + foreach my $from ( sort keys %redirects ) { + my $to = $redirects{$from}; + $wth->browser_get( $base_url . $from ); + my ($got) = $wth->browser_uri() =~ m{(/rss/.*)$}; + + next if $got eq $to; + + warn "RSS redirect from '$from' to '$to' failed - got '$got'"; + $error_count++; + } + + die "Found errors doing redirect - aborting" if $error_count; + + $wth->browser_get($base_url . '/rss/l/52.5/-1.94'); $wth->browser_check_contents('New local problems on FixMyStreet'); - $wth->browser_get($base_url . '/rss/pc/SW1A1AA'); - die "Postcode redirect failed" unless $wth->browser_uri() =~ m{/rss/n/529090,179645$}; - $wth->browser_get($base_url . '/rss/area/Birmingham'); - die "One-tier redirect failed" unless $wth->browser_uri() =~ m{/rss/reports/Birmingham$}; + $wth->browser_get($base_url . '/rss/reports/Birmingham'); $wth->browser_check_contents('New problems to Birmingham City Council on FixMyStreet'); $wth->browser_get($base_url . '/rss/reports/Birmingham/Lozells'); diff --git a/bin/update-areas b/bin/update-areas index ed463708a..bbc933faf 100755 --- a/bin/update-areas +++ b/bin/update-areas @@ -33,11 +33,11 @@ BEGIN { } print "Fetching problems...\n"; -my $ids = select_all("select id, easting, northing from problem where areas=''"); +my $ids = select_all("select id, latitude, longitude from problem where areas=''"); print "Updating areas...\n"; my $c = 0; foreach (@$ids) { - my $areas = mySociety::MaPit::get_voting_areas_by_location({easting=>$_->{easting}, northing=>$_->{northing}}, 'polygon'); + my $areas = mySociety::MaPit::get_voting_areas_by_location({latitude=>$_->{latitude}, longitude=>$_->{longitude}}, 'polygon'); $areas = ',' . join(',', sort keys %$areas) . ','; dbh()->do('update problem set areas=? where id=?', {}, $areas, $_->{id}); dbh()->commit(); diff --git a/conf/general-example b/conf/general-example index 420f98bf5..20872ae28 100644 --- a/conf/general-example +++ b/conf/general-example @@ -27,6 +27,9 @@ define('OPTION_BCI_DB_PASS', ''); define('OPTION_BASE_URL', 'http://www.example.org'); +# Which country are you operating in? ISO3166-alpha2 code please +define('OPTION_COUNTRY', 'GB'); + define('OPTION_TESTING_EMAIL', 'testing@example.com'); define('OPTION_EMAIL_DOMAIN', 'example.org'); define('OPTION_CONTACT_EMAIL', 'team@'.OPTION_EMAIL_DOMAIN); diff --git a/conf/httpd.conf b/conf/httpd.conf index 67170bad5..8bc50645a 100644 --- a/conf/httpd.conf +++ b/conf/httpd.conf @@ -60,14 +60,14 @@ RewriteRule ^/[Tt]/([0-9A-Za-z]{16,18}).*$ /tms-signup.cgi?token=$1 RewriteRule ^/rss/([0-9]+)$ /rss.cgi?type=new_updates;id=$1 [QSA] # RSS feeds for new local problems -RewriteRule ^/rss/([0-9]+)[,/]([0-9]+)$ /rss.cgi?type=local_problems;x=$1;y=$2 [QSA] -RewriteRule ^/rss/n/([0-9]+)[,/]([0-9]+)$ /rss.cgi?type=local_problems;e=$1;n=$2 [QSA] -RewriteRule ^/rss/l/([0-9.-]+)[,/]([0-9.-]+)$ /rss.cgi?type=local_problems;lat=$1;lon=$2 [QSA] -RewriteRule ^/rss/([0-9]+)[,/]([0-9]+)/([0-9]+)$ /rss.cgi?type=local_problems;x=$1;y=$2;d=$3 [QSA] -RewriteRule ^/rss/n/([0-9]+)[,/]([0-9]+)/([0-9]+)$ /rss.cgi?type=local_problems;e=$1;n=$2;d=$3 [QSA] +RewriteRule ^/rss/([0-9]+)[,/]([0-9]+)$ /rss.cgi?type=local_problems;x=$1;y=$2 [QSA] +RewriteRule ^/rss/n/([0-9]+)[,/]([0-9]+)$ /rss.cgi?type=local_problems;e=$1;n=$2 [QSA] +RewriteRule ^/rss/l/([0-9.-]+)[,/]([0-9.-]+)$ /rss.cgi?type=local_problems;lat=$1;lon=$2 [QSA] +RewriteRule ^/rss/([0-9]+)[,/]([0-9]+)/([0-9]+)$ /rss.cgi?type=local_problems;x=$1;y=$2;d=$3 [QSA] +RewriteRule ^/rss/n/([0-9]+)[,/]([0-9]+)/([0-9]+)$ /rss.cgi?type=local_problems;e=$1;n=$2;d=$3 [QSA] RewriteRule ^/rss/l/([0-9.-]+)[,/]([0-9.-]+)/([0-9]+)$ /rss.cgi?type=local_problems;lat=$1;lon=$2;d=$3 [QSA] -RewriteRule ^/rss/pc/(.*)$ /rss.cgi?type=local_problems;pc=$1 [QSA] -RewriteRule ^/rss/problems$ /rss.cgi?type=new_problems [QSA] +RewriteRule ^/rss/pc/(.*)$ /rss.cgi?type=local_problems;pc=$1 [QSA] +RewriteRule ^/rss/problems$ /rss.cgi?type=new_problems [QSA] # RSS feeds for voting areas RewriteRule ^/rss/council/([0-9]+)$ /rss/reports/$1 [R=permanent] diff --git a/db/migrate_from_osgb36_to_wgs84.pl b/db/migrate_from_osgb36_to_wgs84.pl new file mode 100644 index 000000000..62eac8296 --- /dev/null +++ b/db/migrate_from_osgb36_to_wgs84.pl @@ -0,0 +1,201 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +=head1 DESCRIPTION + +This script will take a FMS database with eastings and northings in and migrate +it to latitude and longitude. It touches the following tables and functions: + +=cut + +use FindBin; +use lib "$FindBin::Bin/../perllib"; +use lib "$FindBin::Bin/../commonlib/perllib"; + +use mySociety::Config; +use mySociety::DBHandle qw(dbh); +use Utils; + +BEGIN { + mySociety::Config::set_file("$FindBin::Bin/../conf/general"); + mySociety::DBHandle::configure( + Name => mySociety::Config::get('BCI_DB_NAME'), + User => mySociety::Config::get('BCI_DB_USER'), + Password => mySociety::Config::get('BCI_DB_PASS'), + Host => mySociety::Config::get( 'BCI_DB_HOST', undef ), + Port => mySociety::Config::get( 'BCI_DB_PORT', undef ) + ); +} + +my $UPDATE_BATCH_SIZE = 1; # FIXME - should be ~ 500 + +migrate_problem_table(); +migrate_problem_find_nearby_function(); +migrate_alert_table(); + +=head2 problem table + +Add columns 'latitude' and 'longitude'. +Update all entries coverting from e,n to lon,lat. +Make the lat, lon columns not null. +Drop the 'problem_state_easting_northing_idx' index. +Create new index 'problem_state_latitude_longitude_idx'. +Drop the 'easting' and 'northing' columns. + +=cut + +sub migrate_problem_table { + + my $dbh = dbh(); + + # add columns + print "add latitude, longitude columns\n"; + $dbh->do("ALTER TABLE problem ADD $_ double precision") + for qw(latitude longitude); + $dbh->commit; + + # create a query for rows that need converting + my $rows_to_convert_query = $dbh->prepare( # + "SELECT id, easting, northing FROM problem" + . " WHERE latitude is NULL limit $UPDATE_BATCH_SIZE" + ); + + # update query + my $update_lat_lon_query = $dbh->prepare( # + "UPDATE problem SET latitude = ?, longitude = ? WHERE id = ?" + ); + + # loop through the entries in batches updating rows that need it. Do this in + # Perl rather than SQL for conveniance. + while (1) { + $rows_to_convert_query->execute; + last unless $rows_to_convert_query->rows; + while ( my $r = $rows_to_convert_query->fetchrow_hashref ) { + my ( $latitude, $longitude ) = + Utils::convert_en_to_latlon( $r->{easting}, $r->{northing} ); + print "update problem $r->{id}: ( $latitude, $longitude )\n"; + $update_lat_lon_query->execute( $latitude, $longitude, $r->{id} ); + } + $dbh->commit; # every batch of updates + } + + print "make latitude, longitude columns not null\n"; + $dbh->do("ALTER TABLE problem ALTER COLUMN $_ SET NOT NULL") + for qw(latitude longitude); + $dbh->commit; + + # drop old index, create new one + print "drop and create indexes\n"; + $dbh->do("DROP INDEX problem_state_easting_northing_idx"); + $dbh->do( "CREATE INDEX problem_state_latitude_longitude_idx " + . "ON problem(state, latitude, longitude)" ); + $dbh->commit; + + # drop columns + print "drop easting, northing columns\n"; + $dbh->do("ALTER TABLE problem DROP $_") for qw(easting northing); + $dbh->commit; + +} + +=head2 problem_find_nearby function + +Convert to use lat and long. +Also swap parameter order so that it is lat,lon rather than lon,lat to be consistent with pledgebank etc + +=cut + +sub migrate_problem_find_nearby_function { + my $dbh = dbh(); + + print "drop the existing problem_find_nearby function\n"; + $dbh->do( +"DROP FUNCTION problem_find_nearby ( double precision, double precision, double precision)" + ); + + print "create the new one\n"; + $dbh->do(<<'SQL_END'); + create function problem_find_nearby(double precision, double precision, double precision) + returns setof problem_nearby_match as + ' + -- trunc due to inaccuracies in floating point arithmetic + select problem.id, + R_e() * acos(trunc( + (sin(radians($1)) * sin(radians(latitude)) + + cos(radians($1)) * cos(radians(latitude)) + * cos(radians($2 - longitude)))::numeric, 14) + ) as distance + from problem + where + longitude is not null and latitude is not null + and radians(latitude) > radians($1) - ($3 / R_e()) + and radians(latitude) < radians($1) + ($3 / R_e()) + and (abs(radians($1)) + ($3 / R_e()) > pi() / 2 -- case where search pt is near pole + or angle_between(radians(longitude), radians($2)) + < $3 / (R_e() * cos(radians($1 + $3 / R_e())))) + -- ugly -- unable to use attribute name "distance" here, sadly + and R_e() * acos(trunc( + (sin(radians($1)) * sin(radians(latitude)) + + cos(radians($1)) * cos(radians(latitude)) + * cos(radians($2 - longitude)))::numeric, 14) + ) < $3 + order by distance desc + ' language sql +SQL_END + + $dbh->commit; +} + +=head2 alert table + +NOTE: only for alert_types 'local_problems' or 'local_problems_state' + +parameter: convert easting to longitude +parameter2: convert nothing to latitude + +create a new column 'is_migrated' to use during migration in case of crash. + +=cut + +sub migrate_alert_table { + my $dbh = dbh(); + + print "Adding 'is_migrated' column\n"; + $dbh->do("ALTER TABLE alert ADD COLUMN is_migrated bool DEFAULT false"); + $dbh->commit; + + # create a query for rows that need converting + my $rows_to_convert_query = $dbh->prepare( # + "SELECT id, parameter, parameter2 FROM alert" + . " WHERE alert_type IN ('local_problems','local_problems_state')" + . " AND is_migrated = false" + . " LIMIT $UPDATE_BATCH_SIZE" + ); + + # update query + my $update_lat_lon_query = $dbh->prepare( # + "UPDATE alert SET parameter2 = ?, parameter = ?, is_migrated = true" + . " WHERE id = ?" + ); + + # loop through the entries in batches updating rows that need it. Do this in + # Perl rather than SQL for conveniance. + while (1) { + $rows_to_convert_query->execute; + last unless $rows_to_convert_query->rows; + while ( my $r = $rows_to_convert_query->fetchrow_hashref ) { + my ( $latitude, $longitude ) = + Utils::convert_en_to_latlon( $r->{parameter}, $r->{parameter2} ); + print "update alert $r->{id}: ( $latitude, $longitude )\n"; + $update_lat_lon_query->execute( $latitude, $longitude, $r->{id} ); + } + $dbh->commit; # every batch of updates + } + + print "drop 'is_migrated' column\n"; + $dbh->do("ALTER TABLE alert DROP COLUMN is_migrated"); + $dbh->commit; +} + diff --git a/db/schema.sql b/db/schema.sql index 53d188244..fbff047fb 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -121,8 +121,8 @@ create table problem ( -- Problem details postcode text not null, - easting double precision not null, - northing double precision not null, + latitude double precision not null, + longitude double precision not null, council text, -- the council(s) we'll report this problem to areas text not null, -- the voting areas this location is in category text not null default 'Other', @@ -155,7 +155,7 @@ create table problem ( whensent timestamp, send_questionnaire boolean not null default 't' ); -create index problem_state_easting_northing_idx on problem(state, easting, northing); +create index problem_state_latitude_longitude_idx on problem(state, latitude, longitude); create table questionnaire ( id serial not null primary key, @@ -196,25 +196,41 @@ create type problem_nearby_match as ( distance double precision -- km ); --- problem_find_nearby EASTING NORTHING DISTANCE --- Find problems within DISTANCE (km) of (EASTING, NORTHING). +-- problem_find_nearby LATITUDE LONGITUDE DISTANCE +-- Find locations within DISTANCE (km) of (LATITUDE, LONGITUDE). create function problem_find_nearby(double precision, double precision, double precision) returns setof problem_nearby_match as -- Write as SQL function so that we don't have to construct a temporary -- table or results set in memory. That means we can't check the values of -- the parameters, sadly. + -- Through sheer laziness, just use great-circle distance; that'll be off + -- by ~0.1%: + -- http://www.ga.gov.au/nmd/geodesy/datums/distance.jsp + -- We index locations on lat/lon so that we can select the locations which lie + -- within a wedge of side about 2 * DISTANCE. That cuts down substantially + -- on the amount of work we have to do. ' -- trunc due to inaccuracies in floating point arithmetic select problem.id, - sqrt(($1 - easting) ^ 2 - + ($2 - northing) ^ 2) - as distance + R_e() * acos(trunc( + (sin(radians($1)) * sin(radians(latitude)) + + cos(radians($1)) * cos(radians(latitude)) + * cos(radians($2 - longitude)))::numeric, 14) + ) as distance from problem where + longitude is not null and latitude is not null + and radians(latitude) > radians($1) - ($3 / R_e()) + and radians(latitude) < radians($1) + ($3 / R_e()) + and (abs(radians($1)) + ($3 / R_e()) > pi() / 2 -- case where search pt is near pole + or angle_between(radians(longitude), radians($2)) + < $3 / (R_e() * cos(radians($1 + $3 / R_e())))) -- ugly -- unable to use attribute name "distance" here, sadly - sqrt(($1 - easting) ^ 2 - + ($2 - northing) ^ 2) - < $3 * 1000 + and R_e() * acos(trunc( + (sin(radians($1)) * sin(radians(latitude)) + + cos(radians($1)) * cos(radians(latitude)) + * cos(radians($2 - longitude)))::numeric, 14) + ) < $3 order by distance desc ' language sql; -- should be "stable" rather than volatile per default? @@ -277,7 +293,7 @@ create table alert_type ( create table alert ( id serial not null primary key, alert_type text not null references alert_type(ref), - parameter text, -- e.g. Problem ID for new updates + parameter text, -- e.g. Problem ID for new updates, Longitude for local problem alerts parameter2 text, -- e.g. Latitude for local problem alerts email text not null, confirmed integer not null default 0, diff --git a/notes/INSTALL b/notes/INSTALL index 5254a6982..fcb1f5579 100644 --- a/notes/INSTALL +++ b/notes/INSTALL @@ -82,14 +82,14 @@ mail youremail@example.com # send a test message cp conf/general-example conf/general nano conf/general -# You only need to alter: +# You may need to alter: # * the database connection details # * the OPTION_BASE_URL to be where your test site will run - eg 'http://localhost' # * the OPTION_EVEL_URL to '' - this will cause some emails not to be sent but warned to STDERR instead - proper email handling is being worked on # * the OPTION_SMTP_SMARTHOST to '' if routing mail via ssmtp as described above - otherwise your SMTP server # * set OPTION_EMAIL_VHOST to the same as OPTION_BASE_URL minus the 'http://' - eg 'localhost' # * set OPTION_UPLOAD_CACHE and OPTION_GEO_CACHE to your preferred values - +# * set OPTION_COUNTRY to your ISO3166 alpha2 code ('GB' for the United Kingdom: http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) # SETTING UP THE DATABASES diff --git a/notes/osgb36_to_wgs84_notes.txt b/notes/osgb36_to_wgs84_notes.txt new file mode 100644 index 000000000..3deca19de --- /dev/null +++ b/notes/osgb36_to_wgs84_notes.txt @@ -0,0 +1,77 @@ +These notes are for the FMS migration from OSGB36 to WGS84 coordinates. + +Reference: + + easting === longitude + northing === latitude + + http://en.wikipedia.org/wiki/Ordnance_Survey_National_Grid + http://en.wikipedia.org/wiki/World_Geodetic_System + +DB: + + * dump schema from live db and confirm it matches schema.sql (you never know...) + DONE: use PledgeBank's 'nearby' logic in problem_find_nearby() SQL function + DONE: in 'alerts' table convert parameter and parameter2 if alert is 'local_problems' or 'local_problems_state' + +CODE: + + DONE: update all calls to problem_find_nearby swapping parameter order. + * find all occurences of 'easting', 'northing', $e, $n etc and migrate + + * email me link broken on http://herring/?lat=51.4545595517093;lon=-0.23100115932431 + + * Cobrand.pm + * Cobrands/Barnet/Util.pm + * Cobrands/Emptyhomes/Util.pm + * CronFns.pm + * CrossSell.pm + * EastHantsWSDL.pm + * FixMyStreet/Alert.pm + * FixMyStreet/Geocode.pm + * FixMyStreet/Map.pm + * FixMyStreet/Map/Bing.pm + * FixMyStreet/Map/BingOL.pm + * FixMyStreet/Map/Google.pm + * FixMyStreet/Map/OSM.pm + * FixMyStreet/Map/OSM/CycleMap.pm + * FixMyStreet/Map/OSM/StreetView.pm + * FixMyStreet/Map/Tilma/OL/1_10k.pm + * FixMyStreet/Map/Tilma/OL/StreetView.pm + * FixMyStreet/Map/Tilma/Original.pm + * FixMyStreet/Map/Tilma/Original/1_10k.pm + * FixMyStreet/Map/Tilma/Original/StreetView.pm + * Memcached.pm + * Page.pm + * PoChange.pm + * Problems.pm + * Standard.pm + * Utils.pm + + * web/about.cgi + * web/ajax.cgi + * web/alert.cgi + * web/confirm.cgi + * web/contact.cgi + * web/faq.cgi + * web/flickr.cgi + * web/fun.cgi + * web/import.cgi + * web/index.cgi + * web/json.cgi + * web/photo.cgi + * web/questionnaire.cgi + * web/reports.cgi + * web/rss.cgi + * web/test.cgi + * web/tms-signup.cgi + * web/upload.cgi + + DONE: all scripts checked + +UI maps: + + * Tilma js code will still need eastings and northings + * All other maps should be able to switch to latlon + + diff --git a/perllib/Carp/Always.pm b/perllib/Carp/Always.pm new file mode 100644 index 000000000..68bcaee52 --- /dev/null +++ b/perllib/Carp/Always.pm @@ -0,0 +1,162 @@ + +package Carp::Always; + +use 5.006; +use strict; +use warnings; + +our $VERSION = '0.09'; + +use Carp qw(verbose); # makes carp() cluck and croak() confess + +sub _warn { + if ($_[-1] =~ /\n$/s) { + my $arg = pop @_; + $arg =~ s/ at .*? line .*?\n$//s; + push @_, $arg; + } + warn &Carp::longmess; +} + +sub _die { + if ($_[-1] =~ /\n$/s) { + my $arg = pop @_; + $arg =~ s/ at .*? line .*?\n$//s; + push @_, $arg; + } + die &Carp::longmess; +} + +my %OLD_SIG; + +BEGIN { + @OLD_SIG{qw(__DIE__ __WARN__)} = @SIG{qw(__DIE__ __WARN__)}; + $SIG{__DIE__} = \&_die; + $SIG{__WARN__} = \&_warn; +} + +END { + @SIG{qw(__DIE__ __WARN__)} = @OLD_SIG{qw(__DIE__ __WARN__)}; +} + +1; +__END__ + +=head1 NAME + +Carp::Always - Warns and dies noisily with stack backtraces + +=head1 SYNOPSIS + + use Carp::Always; + +makes every C<warn()> and C<die()> complains loudly in the calling package +and elsewhere. More often used on the command line: + + perl -MCarp::Always script.pl + +=head1 DESCRIPTION + +This module is meant as a debugging aid. It can be +used to make a script complain loudly with stack backtraces +when warn()ing or die()ing. + +Here are how stack backtraces produced by this module +looks: + + # it works for explicit die's and warn's + $ perl -MCarp::Always -e 'sub f { die "arghh" }; sub g { f }; g' + arghh at -e line 1 + main::f() called at -e line 1 + main::g() called at -e line 1 + + # it works for interpreter-thrown failures + $ perl -MCarp::Always -w -e 'sub f { $a = shift; @a = @$a };' \ + -e 'sub g { f(undef) }; g' + Use of uninitialized value in array dereference at -e line 1 + main::f('undef') called at -e line 2 + main::g() called at -e line 2 + +In the implementation, the C<Carp> module does +the heavy work, through C<longmess()>. The +actual implementation sets the signal hooks +C<$SIG{__WARN__}> and C<$SIG{__DIE__}> to +emit the stack backtraces. + +Oh, by the way, C<carp> and C<croak> when requiring/using +the C<Carp> module are also made verbose, behaving +like C<cloak> and C<confess>, respectively. + +=head2 EXPORT + +Nothing at all is exported. + +=head1 ACKNOWLEDGMENTS + +This module was born as a reaction to a release +of L<Acme::JavaTrace> by Sébastien Aperghis-Tramoni. +Sébastien also has a newer module called +L<Devel::SimpleTrace> with the same code and fewer flame +comments on docs. The pruning of the uselessly long +docs of this module were prodded by Michael Schwern. + +Schwern and others told me "the module name stinked" - +it was called C<Carp::Indeed>. After thinking long +and not getting nowhere, I went with nuffin's suggestion +and now it is called C<Carp::Always>. +C<Carp::Indeed> which is now deprecate +lives in its own distribution (which won't go anywhere +but will stay there as a redirection to this module). + +=head1 SEE ALSO + +=over 4 + +=item * + +L<Carp> + +=item * + +L<Acme::JavaTrace> and L<Devel::SimpleTrace> + +=back + +Please report bugs via CPAN RT +http://rt.cpan.org/NoAuth/Bugs.html?Dist=Carp-Always. + +=head1 BUGS + +Every (un)deserving module has its own pet bugs. + +=over 4 + +=item * + +This module does not play well with other modules which fusses +around with C<warn>, C<die>, C<$SIG{'__WARN__'}>, +C<$SIG{'__DIE__'}>. + +=item * + +Test scripts are good. I should write more of these. + +=item * + +I don't know if this module name is still a bug as it was +at the time of C<Carp::Indeed>. + +=back + +=head1 AUTHOR + +Adriano Ferreira, E<lt>ferreira@cpan.orgE<gt> + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2005-2007 by Adriano R. Ferreira + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/perllib/Cobrands/Barnet/Util.pm b/perllib/Cobrands/Barnet/Util.pm index 32973c10c..ad6fa3919 100644 --- a/perllib/Cobrands/Barnet/Util.pm +++ b/perllib/Cobrands/Barnet/Util.pm @@ -104,9 +104,9 @@ sub disambiguate_location { } sub recent_photos { - my ($self, $num, $e, $n, $dist) = @_; + my ($self, $num, $lat, $lon, $dist) = @_; $num = 2 if $num == 3; - return Problems::recent_photos($num, $e, $n, $dist); + return Problems::recent_photos($num, $lat, $lon, $dist); } 1; diff --git a/perllib/FixMyStreet/Alert.pm b/perllib/FixMyStreet/Alert.pm index b9c124741..90a5b1aaa 100644 --- a/perllib/FixMyStreet/Alert.pm +++ b/perllib/FixMyStreet/Alert.pm @@ -184,12 +184,11 @@ sub email_alerts ($) { $query->execute(); while (my $alert = $query->fetchrow_hashref) { next unless (Cobrand::email_host($alert->{cobrand})); - my $e = $alert->{parameter}; - my $n = $alert->{parameter2}; + my $longitude = $alert->{parameter}; + my $latitude = $alert->{parameter2}; $url = Cobrand::base_url_for_emails($alert->{cobrand}, $alert->{cobrand_data}); my ($site_restriction, $site_id) = Cobrand::site_restriction($alert->{cobrand}, $alert->{cobrand_data}); - my ($lat, $lon) = mySociety::GeoUtil::national_grid_to_wgs84($e, $n, 'G'); - my $d = mySociety::Gaze::get_radius_containing_population($lat, $lon, 200000); + my $d = mySociety::Gaze::get_radius_containing_population($latitude, $longitude, 200000); $d = int($d*10+0.5)/10; my $testing_email_clause = "and problem.email <> '$testing_email'" if $testing_email; my %data = ( template => $template, data => '', alert_id => $alert->{id}, alert_email => $alert->{email}, lang => $alert->{lang}, cobrand => $alert->{cobrand}, cobrand_data => $alert->{cobrand_data} ); @@ -202,7 +201,7 @@ sub email_alerts ($) { $site_restriction order by confirmed desc"; $q = dbh()->prepare($q); - $q->execute($e, $n, $d, $alert->{whensubscribed}, $alert->{id}, $alert->{email}); + $q->execute($latitude, $longitude, $d, $alert->{whensubscribed}, $alert->{id}, $alert->{email}); while (my $row = $q->fetchrow_hashref) { dbh()->do('insert into alert_sent (alert_id, parameter) values (?,?)', {}, $alert->{id}, $row->{id}); $data{data} .= $url . "/report/" . $row->{id} . " - $row->{title}\n\n"; @@ -310,9 +309,8 @@ sub generate_rss ($$$;$$$$) { } $item{description} .= ent("\n<br><a href='$cobrand_url'>Report on FixMyStreet</a>"); - if ($row->{easting} && $row->{northing}) { - my ($lat,$lon) = mySociety::GeoUtil::national_grid_to_wgs84($row->{easting}, $row->{northing}, 'G'); - $item{georss} = { point => "$lat $lon" }; + if ($row->{latitude} || $row->{longitude}) { + $item{georss} = { point => "$row->{latitude} $row->{longitude}" }; } $rss->add_item( %item ); } diff --git a/perllib/FixMyStreet/Geocode.pm b/perllib/FixMyStreet/Geocode.pm index 4854411cf..0379169b8 100644 --- a/perllib/FixMyStreet/Geocode.pm +++ b/perllib/FixMyStreet/Geocode.pm @@ -37,40 +37,32 @@ BEGIN { # of the site to diambiguate locations. sub lookup { my ($s, $q) = @_; - my ($easting, $northing, $error); + my ($latitude, $longitude, $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}; + $latitude = $location->{wgs84_lat}; + $longitude = $location->{wgs84_lon}; } } else { - ($easting, $northing, $error) = FixMyStreet::Geocode::string($s, $q); + ($latitude, $longitude, $error) = FixMyStreet::Geocode::string($s, $q); } - return ($easting, $northing, $error); + return ($latitude, $longitude, $error); } sub geocoded_string_coordinates { my ($js, $q) = @_; - my ($easting, $northing, $error); + my ($latitude, $longitude, $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'); - } 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 ($easting, $northing, $error); + } elsif ( $js =~ /"coordinates" *: *\[ *(.*?), *(.*?),/ ) { + $longitude = $1; + $latitude = $2; + } + return ($latitude, $longitude, $error); } # string STRING QUERY @@ -90,7 +82,7 @@ sub string { 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, $easting, $northing); + my ($js, $error, $latitude, $longitude); if (-s $cache_file) { $js = File::Slurp::read_file($cache_file); } else { @@ -123,9 +115,9 @@ sub string { # 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 { - ($easting, $northing, $error) = geocoded_string_coordinates($js, $q); + ($latitude, $longitude, $error) = geocoded_string_coordinates($js, $q); } - return ($easting, $northing, $error); + return ($latitude, $longitude, $error); } # list_choices diff --git a/perllib/FixMyStreet/Map.pm b/perllib/FixMyStreet/Map.pm index 12ecf78fe..0902914dd 100644 --- a/perllib/FixMyStreet/Map.pm +++ b/perllib/FixMyStreet/Map.pm @@ -14,29 +14,34 @@ use Problems; use Cobrand; use mySociety::Config; use mySociety::Gaze; -use mySociety::GeoUtil; +use mySociety::GeoUtil qw(national_grid_to_wgs84); use mySociety::Locale; use mySociety::Web qw(ent NewURL); +use Utils; # Run on module boot up load(); # This is yucky, but no-one's taught me a better way sub load { - my $type = mySociety::Config::get('MAP_TYPE'); + my $type = mySociety::Config::get('MAP_TYPE'); my $class = "FixMyStreet::Map::$type"; eval "use $class"; + + # If we have an error die as it is a compile error rather than runtime error + die $@ if $@; } sub header { - my ($q, $type) = @_; + my ( $q, $type ) = @_; return '' unless $type; my $cobrand = Page::get_cobrand($q); - my $cobrand_form_elements = Cobrand::form_elements($cobrand, 'mapForm', $q); - my $form_action = Cobrand::url($cobrand, '', $q); + my $cobrand_form_elements = + Cobrand::form_elements( $cobrand, 'mapForm', $q ); + my $form_action = Cobrand::url( $cobrand, '', $q ); my $encoding = ''; - $encoding = ' enctype="multipart/form-data"' if $type==2; + $encoding = ' enctype="multipart/form-data"' if $type == 2; my $pc = $q->param('pc') || ''; my $pc_enc = ent($pc); return <<EOF; @@ -47,49 +52,67 @@ $cobrand_form_elements EOF } +=head2 map_features_easting_northing + +Wrapper around map_features which does the easting, northing to lat, lon +conversion. + +=cut + +sub map_features_easting_northing { + my ( $q, $easting, $northing, $interval ) = @_; + my ( $lat, $lon ) = Utils::convert_en_to_latlon( $easting, $northing ); + return map_features( $q, $lat, $lon, $interval ); +} + sub map_features { - my ($q, $easting, $northing, $interval) = @_; - - my $min_e = $easting - 500; - my $min_n = $northing - 500; - my $mid_e = $easting; - my $mid_n = $northing; - my $max_e = $easting + 500; - my $max_n = $northing + 500; - - # list of problems aoround map can be limited, but should show all pins - my ($around_map, $around_map_list); - if (my $around_limit = Cobrand::on_map_list_limit(Page::get_cobrand($q))) { - $around_map_list = Problems::around_map($min_e, $max_e, $min_n, $max_n, $interval, $around_limit); - $around_map = Problems::around_map($min_e, $max_e, $min_n, $max_n, $interval, undef); - } else { - $around_map = $around_map_list = Problems::around_map($min_e, $max_e, $min_n, $max_n, $interval, undef); - } + my ( $q, $lat, $lon, $interval ) = @_; + + # TODO - be smarter about calculating the surrounding square + # use deltas that are roughly 500m in the UK - so we get a 1 sq km search box + my $lat_delta = 0.00438; + my $lon_delta = 0.00736; + + my $min_lat = $lat - $lat_delta; + my $max_lat = $lat + $lat_delta; + + my $min_lon = $lon - $lon_delta; + my $max_lon = $lon + $lon_delta; + + # list of problems around map can be limited, but should show all pins + my $around_limit # + = Cobrand::on_map_list_limit( Page::get_cobrand($q) ) || undef; + + my @around_args = ( $min_lat, $max_lat, $min_lon, $max_lon, $interval ); + my $around_map_list = Problems::around_map( @around_args, $around_limit ); + my $around_map = Problems::around_map( @around_args, undef ); my $dist; mySociety::Locale::in_gb_locale { - my ($lat, $lon) = mySociety::GeoUtil::national_grid_to_wgs84($mid_e, $mid_n, 'G'); - $dist = mySociety::Gaze::get_radius_containing_population($lat, $lon, 200000); + $dist = + mySociety::Gaze::get_radius_containing_population( $lat, $lon, + 200000 ); }; - $dist = int($dist*10+0.5)/10; + $dist = int( $dist * 10 + 0.5 ) / 10; - my $limit = 20; - my @ids = map { $_->{id} } @$around_map_list; - my $nearby = Problems::nearby($dist, join(',', @ids), $limit, $mid_e, $mid_n, $interval); + my $limit = 20; + my @ids = map { $_->{id} } @$around_map_list; + my $nearby = Problems::nearby( $dist, join( ',', @ids ), + $limit, $lat, $lon, $interval ); - return ($around_map, $around_map_list, $nearby, $dist); + return ( $around_map, $around_map_list, $nearby, $dist ); } sub compass ($$$) { - my ($q, $x, $y) = @_; + my ( $q, $x, $y ) = @_; my @compass; - for (my $i=$x-1; $i<=$x+1; $i++) { - for (my $j=$y-1; $j<=$y+1; $j++) { - $compass[$i][$j] = NewURL($q, x=>$i, y=>$j); + for ( my $i = $x - 1 ; $i <= $x + 1 ; $i++ ) { + for ( my $j = $y - 1 ; $j <= $y + 1 ; $j++ ) { + $compass[$i][$j] = NewURL( $q, x => $i, y => $j ); } } my $recentre = NewURL($q); - my $host = Page::base_url_with_lang($q, undef); + my $host = Page::base_url_with_lang( $q, undef ); return <<EOF; <table cellpadding="0" cellspacing="0" border="0" id="compass"> <tr valign="bottom"> diff --git a/perllib/FixMyStreet/Map/Tilma/Original.pm b/perllib/FixMyStreet/Map/Tilma/Original.pm index 5772f6ccd..81b123b30 100644 --- a/perllib/FixMyStreet/Map/Tilma/Original.pm +++ b/perllib/FixMyStreet/Map/Tilma/Original.pm @@ -13,6 +13,13 @@ use LWP::Simple; use Cobrand; use mySociety::Web qw(ent NewURL); +use mySociety::GeoUtil; +use Utils; + +sub _ll_to_en { + my ($lat, $lon) = @_; + return mySociety::GeoUtil::wgs84_to_national_grid( $lat, $lon, 'G' ); +} sub header_js { return ' @@ -22,7 +29,7 @@ sub header_js { # display_map Q PARAMS # PARAMS include: -# EASTING, NORTHING for the centre point of the map +# latitude, longitude for the centre point of the map # TYPE is 1 if the map is clickable, 2 if clickable and has a form upload, # 0 if not clickable # PINS is array of pins to show, location and colour @@ -36,6 +43,18 @@ sub _display_map { $mid_point = 189; } + # convert map center point to easting, northing + ( $params{easting}, $params{northing} ) = + _ll_to_en( $params{latitude}, $params{longitude} ); + + # FIXME - convert all pins to lat, lng + # all the pins are currently [lat, lng, colour] - convert them + foreach my $pin ( @{ $params{pins} ||= [] } ) { + my ( $lat, $lon ) = ( $pin->[0], $pin->[1] ); + my ( $e, $n ) = _ll_to_en( $lat, $lon ); + ( $pin->[0], $pin->[1] ) = ( $e, $n ); + } + # X/Y tile co-ords may be overridden in the query string my @vars = qw(x y); my %input = map { $_ => $q->param($_) || '' } @vars; @@ -154,10 +173,14 @@ sub map_pins { my $e = FixMyStreet::Map::tile_to_os($x); my $n = FixMyStreet::Map::tile_to_os($y); - my ($around_map, $around_map_list, $nearby, $dist) = FixMyStreet::Map::map_features($q, $e, $n, $interval); + + my ( $around_map, $around_map_list, $nearby, $dist ) = + FixMyStreet::Map::map_features_easting_northing( $q, $e, $n, $interval ); my $pins = ''; foreach (@$around_map) { + ( $_->{easting}, $_->{northing} ) = + _ll_to_en( $_->{latitude}, $_->{longitude} ); my $px = FixMyStreet::Map::os_to_px($_->{easting}, $sx); my $py = FixMyStreet::Map::os_to_px($_->{northing}, $sy, 1); my $col = $_->{state} eq 'fixed' ? 'green' : 'red'; @@ -165,6 +188,8 @@ sub map_pins { } foreach (@$nearby) { + ( $_->{easting}, $_->{northing} ) = + _ll_to_en( $_->{latitude}, $_->{longitude} ); my $px = FixMyStreet::Map::os_to_px($_->{easting}, $sx); my $py = FixMyStreet::Map::os_to_px($_->{northing}, $sy, 1); my $col = $_->{state} eq 'fixed' ? 'green' : 'red'; @@ -196,10 +221,30 @@ sub tile_to_px { sub os_to_tile { return $_[0] / SCALE_FACTOR; } + sub tile_to_os { return int($_[0] * SCALE_FACTOR + 0.5); } +=head2 tile_xy_to_wgs84 + + ($lat, $lon) = tile_xy_to_wgs84( $x, $y ); + +Takes the tile x,y and converts to lat, lon. + +=cut + +sub tile_xy_to_wgs84 { + my ( $x, $y ) = @_; + + my $easting = tile_to_os($x); + my $northing = tile_to_os($y); + + my ( $lat, $lon ) = Utils::convert_en_to_latlon( $easting, $northing ); + return ( $lat, $lon ); +} + + sub click_to_tile { my ($pin_tile, $pin, $invert) = @_; $pin -= TILE_WIDTH while $pin > TILE_WIDTH; @@ -219,6 +264,14 @@ sub click_to_os { return ($easting, $northing); } +# Given some click co-ords (the tile they were on, and where in the +# tile they were), convert to WGS84 and return. +sub click_to_wgs84 { + my ( $easting, $northing ) = FixMyStreet::Map::click_to_os(@_); + my ( $lat, $lon ) = national_grid_to_wgs84( $easting, $northing, 'G' ); + return ( $lat, $lon ); +} + # Given (E,N) and potential override (X,Y), return the X/Y tile for the centre # of the map (either to get the point near the middle, or the override X,Y), # and the pixel co-ords of the point, relative to that map. diff --git a/perllib/Page.pm b/perllib/Page.pm index b3f320f5d..fc7127a78 100644 --- a/perllib/Page.pm +++ b/perllib/Page.pm @@ -80,6 +80,9 @@ sub report_error { my $trylater = sprintf(_('Please try again later, or <a href="mailto:%s">email us</a> to let us know.'), $contact_email); my $somethingwrong = _("Sorry! Something's gone wrong."); my $errortext = _("The text of the error was:"); + + my $msg_br = join '<br><br>', split m{\n}, $msg; + print "Status: 500\nContent-Type: text/html; charset=iso-8859-1\n\n", qq(<html><head><title>$somethingwrong</title></head></html>), q(<body>), @@ -87,7 +90,7 @@ sub report_error { qq(<p>$trylater</p>), q(<hr>), qq(<p>$errortext</p>), - qq(<blockquote class="errortext">$msg</blockquote>), + qq(<blockquote class="errortext">$msg_br</blockquote>), q(</body></html>); } diff --git a/perllib/Problems.pm b/perllib/Problems.pm index 1556b7724..8c6eeccad 100644 --- a/perllib/Problems.pm +++ b/perllib/Problems.pm @@ -87,10 +87,10 @@ sub recent_new { # Front page recent lists sub recent_photos { - my ($num, $e, $n, $dist) = @_; + my ($num, $lat, $lon, $dist) = @_; my $probs; - if ($e) { - my $key = "recent_photos:$site_key:$num:$e:$n:$dist"; + if (defined $lat) { + my $key = "recent_photos:$site_key:$num:$lat:$lon:$dist"; $probs = Memcached::get($key); unless ($probs) { $probs = select_all("select id, title @@ -98,7 +98,7 @@ sub recent_photos { where nearby.problem_id = problem.id and state in ('confirmed', 'fixed') and photo is not null $site_restriction - order by confirmed desc limit $num", $e, $n, $dist); + order by confirmed desc limit $num", $lat, $lon, $dist); Memcached::set($key, $probs, 3600); } } else { @@ -170,46 +170,46 @@ sub front_stats { # Problems around a location sub around_map { - my ($min_e, $max_e, $min_n, $max_n, $interval, $limit) = @_; + my ($min_lat, $max_lat, $min_lon, $max_lon, $interval, $limit) = @_; my $limit_clause = ''; if ($limit) { $limit_clause = " limit $limit"; } mySociety::Locale::in_gb_locale { select_all( - "select id,title,easting,northing,state, + "select id,title,latitude,longitude,state, extract(epoch from confirmed) as time from problem where state in ('confirmed', 'fixed') - and easting>=? and easting<? and northing>=? and northing<? " . + and latitude>=? and latitude<? and longitude>=? and longitude<? " . ($interval ? " and ms_current_timestamp()-lastupdate < '$interval'::interval" : '') . " $site_restriction order by created desc - $limit_clause", $min_e, $max_e, $min_n, $max_n); + $limit_clause", $min_lat, $max_lat, $min_lon, $max_lon); }; } sub nearby { - my ($dist, $ids, $limit, $mid_e, $mid_n, $interval) = @_; + my ($dist, $ids, $limit, $mid_lat, $mid_lon, $interval) = @_; mySociety::Locale::in_gb_locale { select_all( - "select id, title, easting, northing, distance, state, + "select id, title, latitude, longitude, distance, state, extract(epoch from confirmed) as time from problem_find_nearby(?, ?, $dist) as nearby, problem where nearby.problem_id = problem.id " . ($interval ? " and ms_current_timestamp()-lastupdate < '$interval'::interval" : '') . " and state in ('confirmed', 'fixed')" . ($ids ? ' and id not in (' . $ids . ')' : '') . " $site_restriction - order by distance, created desc limit $limit", $mid_e, $mid_n); + order by distance, created desc limit $limit", $mid_lat, $mid_lon); } } sub fixed_nearby { - my ($dist, $mid_e, $mid_n) = @_; + my ($dist, $mid_lat, $mid_lon) = @_; mySociety::Locale::in_gb_locale { select_all( - "select id, title, easting, northing, distance + "select id, title, latitude, longitude, distance from problem_find_nearby(?, ?, $dist) as nearby, problem where nearby.problem_id = problem.id and state='fixed' $site_restriction - order by lastupdate desc", $mid_e, $mid_n); + order by lastupdate desc", $mid_lat, $mid_lon); } } @@ -218,7 +218,7 @@ sub fixed_nearby { sub fetch_problem { my $id = shift; my $p = dbh()->selectrow_hashref( - "select id, easting, northing, council, category, title, detail, photo, + "select id, latitude, longitude, council, category, title, detail, photo, used_map, name, anonymous, extract(epoch from confirmed) as time, state, extract(epoch from whensent-confirmed) as whensent, extract(epoch from ms_current_timestamp()-lastupdate) as duration, diff --git a/perllib/Utils.pm b/perllib/Utils.pm index 24f4a6f94..d54e081c8 100644 --- a/perllib/Utils.pm +++ b/perllib/Utils.pm @@ -13,18 +13,76 @@ package Utils; use strict; use mySociety::DBHandle qw(dbh); +use mySociety::GeoUtil; sub workaround_pg_bytea { - my ($st, $img_idx, @elements) = @_; + my ( $st, $img_idx, @elements ) = @_; my $s = dbh()->prepare($st); - for (my $i=1; $i<=@elements; $i++) { - if ($i == $img_idx) { - $s->bind_param($i, $elements[$i-1], { pg_type => DBD::Pg::PG_BYTEA }); - } else { - $s->bind_param($i, $elements[$i-1]); + for ( my $i = 1 ; $i <= @elements ; $i++ ) { + if ( $i == $img_idx ) { + $s->bind_param( + $i, + $elements[ $i - 1 ], + { pg_type => DBD::Pg::PG_BYTEA } + ); + } + else { + $s->bind_param( $i, $elements[ $i - 1 ] ); } } $s->execute(); } +=head2 convert_en_to_latlon + + ( $latitude, $longitude ) = Utils::convert_en_to_latlon( $easting, $northing ); + +Takes the easting and northing and returns latitude and longitude. + +=cut + +sub convert_en_to_latlon { + my ( $easting, $northing ) = @_; + + my ( $latitude, $longitude ) = + + # map { truncate_coordinate($_) } + mySociety::GeoUtil::national_grid_to_wgs84( $easting, $northing, 'G' ); + + return ( $latitude, $longitude ); +} + +=head2 convert_en_to_latlon_truncated + + ( $lat, $lon ) = Utils::convert_en_to_latlon( $easting, $northing ); + +Takes the easting and northing and returns latitude and longitude (truncated +using C<Utils::truncate_coordinate>). + +=cut + +sub convert_en_to_latlon_truncated { + my ( $easting, $northing ) = @_; + + return + map { truncate_coordinate($_) } + convert_en_to_latlon( $easting, $northing ); +} + +=head2 truncate_coordinate + + $short = Utils::truncate_coordinate( $long ); + +Given a long coordinate returns a shorter one - rounded to 6 decimal places - +which is < 1m at the equator. + +=cut + +sub truncate_coordinate { + my $in = shift; + my $out = sprintf( '%0.6f', $in ); + $out =~ s{\.?0+\z}{} if $out =~ m{\.}; + return $out; +} + 1; @@ -38,17 +38,24 @@ sub set_lang($) { sub test_geocode_string() { my %params = (); - my $q = new MockQuery('nosite', \%params); - - # geocode a straightforward string, expect success - my ($easting, $northing, $error) = FixMyStreet::Geocode::string('Buckingham Palace', $q); - ok($easting == 529068, 'example easting generated') or diag("Got $easting"); - ok($northing == 179684, '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 - ($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"); + my $q = new MockQuery( 'nosite', \%params ); + + # geocode a straightforward string, expect success + my ( $latitude, $longitude, $error ) = + FixMyStreet::Geocode::string( 'Buckingham Palace', $q ); + is( $latitude, 51.5013639, 'example easting generated' ); + is( $longitude, -0.1418898, 'example northing generated' ); + is( $error, undef, 'should not generate error for simple example' ); + # expect a failure message for Northern Ireland + ( $latitude, $longitude, $error ) = + FixMyStreet::Geocode::string( 'Falls Road, Belfast', $q ); + is( + $error, + "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' + ); } sub test_header() { diff --git a/t/utils.t b/t/utils.t new file mode 100644 index 000000000..385c482ed --- /dev/null +++ b/t/utils.t @@ -0,0 +1,41 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Test::More tests => 9; + +use FindBin; +use lib "$FindBin::Bin/../perllib"; +use lib "$FindBin::Bin/../commonlib/perllib"; + +use Utils; + +my @truncate_tests = ( + [ '1.1234567890123', '1.123457', "truncate down" ], + [ '1.123456', '1.123456', "leave untouched" ], + [ '1.12', '1.12', "don't extend" ], + [ '1.100000001', '1.1', "knock off trailing zeros" ], + [ '1.000000001', '1', "knock off trailing zeros" ], + [ '0.0', '0', "knock off trailing zeros" ], + [ '+123', '123', "drop plus sign" ], + [ '-123', '-123', "keep minus sign" ], +); + +foreach my $test (@truncate_tests) { + my ( $in, $out, $msg ) = @$test; + is Utils::truncate_coordinate($in), $out, $msg; +} + +my @convert_en_to_latlon_tests = ( + + # e n lat lon + [ 1234, 4567, 49.808509, -7.544784 ], +); + +foreach my $test (@convert_en_to_latlon_tests) { + my ( $e, $n, $lat, $lon ) = @$test; + is_deeply # + [ Utils::convert_en_to_latlon_truncated( $e, $n ) ], # + [ $lat, $lon ], # + "convert ($e,$n) to ($lat,$lon)"; +} diff --git a/templates/emails/submit-brent b/templates/emails/submit-brent index 6cb226209..f3a9e5bb8 100644 --- a/templates/emails/submit-brent +++ b/templates/emails/submit-brent @@ -21,9 +21,9 @@ Email: <?=$values['email']?> Details: <?=$values['detail']?> -Easting: <?=$values['easting']?> +<?=$values['easting_northing']?>Latitude: <?=$values['latitude']?> -Northing: <?=$values['northing']?> +Longitude: <?=$values['longitude']?> <?=$values['closest_address_machine']?>---------- diff --git a/templates/emails/submit-council b/templates/emails/submit-council index d3a4a0c64..21c89119d 100644 --- a/templates/emails/submit-council +++ b/templates/emails/submit-council @@ -21,9 +21,9 @@ Email: <?=$values['email']?> Details: <?=$values['detail']?> -Easting: <?=$values['easting']?> +<?=$values['easting_northing']?>Latitude: <?=$values['latitude']?> -Northing: <?=$values['northing']?> +Longitude: <?=$values['longitude']?> <?=$values['closest_address']?>---------- diff --git a/web-admin/index.cgi b/web-admin/index.cgi index 2868cf2d6..1044f07c5 100755 --- a/web-admin/index.cgi +++ b/web-admin/index.cgi @@ -574,8 +574,8 @@ sub admin_edit_report { my $council = $row{council} || '<em>None</em>'; (my $areas = $row{areas}) =~ s/^,(.*),$/$1/; - my $easting = int($row{easting}+0.5); - my $northing = int($row{northing}+0.5); + my $latitude = $row{latitude}; + my $longitude = $row{longitude}; my $questionnaire = $row{send_questionnaire} ? 'Yes' : 'No'; my $used_map = $row{used_map} ? 'used map' : "didn't use map"; (my $whensent = $row{whensent} || ' ') =~ s/\..*//; @@ -610,7 +610,7 @@ sub admin_edit_report { <li><a href="$url">View report on site</a> <li><label for="title">Subject:</label> <input size=60 type="text" id="title" name="title" value="$row_h{title}"> <li><label for="detail">Details:</label><br><textarea name="detail" id="detail" cols=60 rows=10>$row_h{detail}</textarea> -<li>Co-ordinates: $easting,$northing (originally entered $row_h{postcode}, $used_map) +<li>Co-ordinates: $latitude,$longitude (originally entered $row_h{postcode}, $used_map) <li>For council(s): $council (other areas: $areas) <li>$anon <li>$state diff --git a/web/alert.cgi b/web/alert.cgi index 71249759e..896ca9ccc 100755 --- a/web/alert.cgi +++ b/web/alert.cgi @@ -24,6 +24,7 @@ use mySociety::MaPit; use mySociety::VotingArea; use mySociety::Web qw(ent); use Cobrand; +use Utils; sub main { my $q = shift; @@ -56,7 +57,7 @@ EOF } elsif ($q->param('type') && $q->param('feed')) { $title = _('Local RSS feeds and email alerts'); $out = alert_local_form($q); - } elsif ($q->param('pc') || ($q->param('e') && $q->param('n'))) { + } elsif ($q->param('pc') || ($q->param('lat') || $q->param('lon'))) { $title = _('Local RSS feeds and email alerts'); $out = alert_list($q); } else { @@ -72,21 +73,25 @@ Page::do_fastcgi(\&main); sub alert_list { my ($q, @errors) = @_; - my @vars = qw(pc rznvy e n); + my @vars = qw(pc rznvy lat lon); my %input = map { $_ => scalar $q->param($_) } @vars; my %input_h = map { $_ => $q->param($_) ? ent($q->param($_)) : '' } @vars; - my($error, $e, $n); - if ($input{e} || $input{n}) { - $e = $input{e}; - $n = $input{n}; + my($error, $lat, $lon); + if ($input{lat} || $input{lon}) { + $lat = $input{lat}; + $lon = $input{lon}; } else { try { - ($e, $n, $error) = FixMyStreet::Geocode::lookup($input{pc}, $q); + ($lat, $lon, $error) = FixMyStreet::Geocode::lookup($input{pc}, $q); } catch Error::Simple with { $error = shift; }; } + + # truncate the lat,lon for nicer urls + ( $lat, $lon ) = map { Utils::truncate_coordinate($_) } ( $lat, $lon ); + return FixMyStreet::Geocode::list_choices($error, '/alert', $q) if ref($error) eq 'ARRAY'; return alert_front_page($q, $error) if $error; @@ -102,7 +107,7 @@ sub alert_list { my @types = (@$mySociety::VotingArea::council_parent_types, @$mySociety::VotingArea::council_child_types); my %councils = map { $_ => 1 } @$mySociety::VotingArea::council_parent_types; - my $areas = mySociety::MaPit::call('point', "27700/$e,$n", type => \@types); + my $areas = mySociety::MaPit::call('point', "4326/$lon,$lat", type => \@types); my $cobrand = Page::get_cobrand($q); my ($success, $error_msg) = Cobrand::council_check($cobrand, { all_councils => $areas }, $q, 'alert'); if (!$success){ @@ -194,18 +199,17 @@ for the county council.'))) . '</div><div id="rss_buttons">'; } } else { # Hopefully impossible in the UK! - throw Error::Simple('An area with three tiers of council? Impossible! '. $e . ' ' . $n . ' ' . join('|',keys %$areas)); + throw Error::Simple('An area with three tiers of council? Impossible! '. $lat . ' ' . $lon . ' ' . join('|',keys %$areas)); } - my ($lat, $lon) = mySociety::GeoUtil::national_grid_to_wgs84($e, $n, 'G'); my $dist = mySociety::Gaze::get_radius_containing_population($lat, $lon, 200000); $dist = int($dist * 10 + 0.5); $dist = $dist / 10.0; my $checked = ''; - $checked = ' checked' if $q->param('feed') && $q->param('feed') eq "local:$e:$n"; + $checked = ' checked' if $q->param('feed') && $q->param('feed') eq "local:$lat:$lon"; my $cobrand_form_elements = Cobrand::form_elements($cobrand, 'alerts', $q); - my $pics = Cobrand::recent_photos($cobrand, 5, $e, $n, $dist); + my $pics = Cobrand::recent_photos($cobrand, 5, $lat, $lon, $dist); $pics = '<div id="alert_photos">' . $q->h2(_('Photos of recent nearby reports')) . $pics . '</div>' if $pics; my $header; if ($pretty_pc) { @@ -231,20 +235,20 @@ feed, or enter your email address to subscribe to an email alert.')); my $rss_label = sprintf(_('Problems within %skm of this location'), $dist); $out .= <<EOF; <p id="rss_local"> -<input type="radio" name="feed" id="local:$e:$n" value="local:$e:$n"$checked> -<label for="local:$e:$n">$rss_label</label> +<input type="radio" name="feed" id="local:$lat:$lon" value="local:$lat:$lon"$checked> +<label for="local:$lat:$lon">$rss_label</label> EOF - my $rss_feed = Cobrand::url($cobrand, "/rss/n/$e,$n", $q); - my $default_link = Cobrand::url($cobrand, "/alert?type=local;feed=local:$e:$n", $q); + my $rss_feed = Cobrand::url($cobrand, "/rss/l/$lat,$lon", $q); + my $default_link = Cobrand::url($cobrand, "/alert?type=local;feed=local:$lat:$lon", $q); my $rss_details = _('(a default distance which covers roughly 200,000 people)'); $out .= $rss_details; $out .= " <a href='$rss_feed'><img src='/i/feed.png' width='16' height='16' title='" . _('RSS feed of nearby problems') . "' alt='" . _('RSS feed') . "' border='0'></a>"; $out .= '</p> <p id="rss_local_alt">' . _('(alternatively the RSS feed can be customised, within'); - my $rss_feed_2k = Cobrand::url($cobrand, "/rss/n/$e,$n/2", $q); - my $rss_feed_5k = Cobrand::url($cobrand, "/rss/n/$e,$n/5", $q); - my $rss_feed_10k = Cobrand::url($cobrand, "/rss/n/$e,$n/10", $q); - my $rss_feed_20k = Cobrand::url($cobrand, "/rss/n/$e,$n/20", $q); + my $rss_feed_2k = Cobrand::url($cobrand, "/rss/l/$lat,$lon/2", $q); + my $rss_feed_5k = Cobrand::url($cobrand, "/rss/l/$lat,$lon/5", $q); + my $rss_feed_10k = Cobrand::url($cobrand, "/rss/l/$lat,$lon/10", $q); + my $rss_feed_20k = Cobrand::url($cobrand, "/rss/l/$lat,$lon/20", $q); $out .= <<EOF; <a href="$rss_feed_2k">2km</a> / <a href="$rss_feed_5k">5km</a> / <a href="$rss_feed_10k">10km</a> / <a href="$rss_feed_20k">20km</a>) @@ -271,8 +275,8 @@ EOF rss_feed_5k => $rss_feed_5k, rss_feed_10k => $rss_feed_10k, rss_feed_20k => $rss_feed_20k, - e => $e, - n => $n, + lat => $lat, + lon => $lon, options => $options ); my $cobrand_page = Page::template_include('alert-options', $q, Page::template_root($q), %vars); $out = $cobrand_page if ($cobrand_page); @@ -526,8 +530,10 @@ sub alert_do_subscribe { $alert_id = FixMyStreet::Alert::create($email, 'council_problems', $cobrand, $cobrand_data, $1, $1); } elsif ($feed =~ /^ward:(\d+):(\d+)/) { $alert_id = FixMyStreet::Alert::create($email, 'ward_problems', $cobrand, $cobrand_data, $1, $2); - } elsif ($feed =~ /^local:(\d+):(\d+)/) { - $alert_id = FixMyStreet::Alert::create($email, 'local_problems', $cobrand, $cobrand_data, $1, $2); + } elsif ($feed =~ m{ \A local: ( [\+\-]? \d+ \.? \d* ) : ( [\+\-]? \d+ \.? \d* ) }xms ) { + my $lat = $1; + my $lon = $2; + $alert_id = FixMyStreet::Alert::create($email, 'local_problems', $cobrand, $cobrand_data, $lon, $lat); } } else { throw FixMyStreet::Alert::Error('Invalid type'); diff --git a/web/import.cgi b/web/import.cgi index 2aede1697..e7746f589 100755 --- a/web/import.cgi +++ b/web/import.cgi @@ -13,16 +13,16 @@ use Error qw(:try); use Standard; use Utils; use mySociety::AuthToken; +use mySociety::Config; use mySociety::EmailUtil; use mySociety::EvEl; +use mySociety::GeoUtil; sub main { my $q = shift; my @vars = qw(service subject detail name email phone easting northing lat lon id phone_id); my %input = map { $_ => $q->param($_) || '' } @vars; - $input{easting} ||= 0; - $input{northing} ||= 0; my @errors; unless ($ENV{REQUEST_METHOD} eq 'POST') { @@ -32,6 +32,18 @@ sub main { return; } + # If we were given easting, northing convert to lat lon now + my $latitude = $input{lat} ||= 0; + my $longitude = $input{lon} ||= 0; + if ( + !( $latitude || $longitude ) # have not been given lat or lon + && ( $input{easting} && $input{northing} ) # but do have e and n + ) + { + ( $latitude, $longitude ) = + Utils::convert_en_to_latlon( $input{easting}, $input{northing}); + } + my $fh = $q->upload('photo'); # MUST come before $q->header, don't know why! print $q->header(-charset => 'utf-8', -content_type => 'text/plain'); @@ -50,14 +62,17 @@ sub main { push @errors, 'Please enter a valid email'; } - if ($input{lat}) { + if ( $latitude && mySociety::Config::get('COUNTRY') eq 'GB' ) { try { - ($input{easting}, $input{northing}) = mySociety::GeoUtil::wgs84_to_national_grid($input{lat}, $input{lon}, 'G'); - } catch Error::Simple with { + mySociety::GeoUtil::wgs84_to_national_grid( $latitude, $longitude, + 'G' ); + } + catch Error::Simple with { my $e = shift; push @errors, "We had a problem with the supplied co-ordinates - outside the UK?"; }; } + # TODO: Get location from photo if present in EXIF data? my $photo; @@ -70,7 +85,7 @@ sub main { }; } - unless ($photo || ($input{easting} && $input{northing})) { + unless ( $photo || ( $latitude || $longitude ) ) { push @errors, 'Either a location or a photo must be provided.'; } @@ -92,11 +107,11 @@ sub main { # Store what we have so far in the database my $id = dbh()->selectrow_array("select nextval('problem_id_seq')"); Utils::workaround_pg_bytea("insert into problem - (id, postcode, easting, northing, title, detail, name, service, + (id, postcode, latitude, longitude, title, detail, name, service, email, phone, photo, state, used_map, anonymous, category, areas) values (?, '', ?, ?, ?, ?, ?, ?, ?, ?, ?, 'partial', 't', 'f', '', '')", 10, - $id, $input{easting}, $input{northing}, $input{subject}, + $id, $latitude, $longitude, $input{subject}, $input{detail}, $input{name}, $input{service}, $input{email}, $input{phone}, $photo); # Send checking email diff --git a/web/index.cgi b/web/index.cgi index e503f50ba..62a882e6b 100755 --- a/web/index.cgi +++ b/web/index.cgi @@ -16,6 +16,8 @@ use RABX; use CGI::Carp; use URI::Escape; +# use Carp::Always; + use CrossSell; use FixMyStreet::Geocode; use mySociety::AuthToken; @@ -29,6 +31,13 @@ use mySociety::PostcodeUtil; use mySociety::Random; use mySociety::VotingArea; use mySociety::Web qw(ent NewURL); +use Utils; + +sub debug (@) { + return; + my ( $format, @args ) = @_; + warn sprintf $format, map { defined $_ ? $_ : 'undef' } @args; +} BEGIN { if (!dbh()->selectrow_array('select secret from secret for update of secret')) { @@ -47,13 +56,13 @@ sub main { my $id = mySociety::AuthToken::retrieve('partial', $partial); if ($id) { my @row = dbh()->selectrow_array( - "select easting, northing, name, email, title, (photo is not null) as has_photo, phone, detail + "select latitude, longitude, name, email, title, (photo is not null) as has_photo, phone, detail from problem where id=? and state='partial'", {}, $id); if (@row) { $q->param('anonymous', 1); $q->param('submit_map', 1); - $q->param('easting', $row[0]); - $q->param('northing', $row[1]); + $q->param('latitude', $row[0]); + $q->param('longitude', $row[1]); $q->param('name', $row[2]); $q->param('email', $row[3]); $q->param('title', $row[4]); @@ -82,9 +91,11 @@ sub main { } elsif ($q->param('id')) { ($out, %params) = display_problem($q, [], {}); $params{title} .= ' - ' . _('Viewing a problem'); - } elsif ($q->param('pc') || ($q->param('x') && $q->param('y')) || ($q->param('e') && $q->param('n'))) { + } elsif ($q->param('pc') || ($q->param('x') && $q->param('y')) || ($q->param('lat') || $q->param('lon'))) { ($out, %params) = display_location($q); $params{title} = _('Viewing a location'); + } elsif ($q->param('e') && $q->param('n')) { + ($out, %params) = redirect_from_osgb_to_wgs84($q); } else { ($out, %params) = front_page($q); } @@ -253,7 +264,7 @@ sub submit_update { sub submit_problem { my $q = shift; - my @vars = qw(council title detail name email phone pc easting northing skipped anonymous category partial upload_fileid lat lon); + my @vars = qw(council title detail name email phone pc skipped anonymous category partial upload_fileid latitude longitude); my %input = map { $_ => scalar $q->param($_) } @vars; for (qw(title detail)) { $input{$_} = lc $input{$_} if $input{$_} !~ /[a-z]/; @@ -265,15 +276,17 @@ sub submit_problem { my @errors; my %field_errors; - if ($input{lat}) { + + # If in UK and we have a lat,lon coocdinate check it is in UK + if ( $input{latitude} && mySociety::Config::get('COUNTRY') eq 'GB' ) { try { - ($input{easting}, $input{northing}) = mySociety::GeoUtil::wgs84_to_national_grid($input{lat}, $input{lon}, 'G'); + mySociety::GeoUtil::wgs84_to_national_grid($input{latitude}, $input{longitude}, 'G'); } catch Error::Simple with { my $e = shift; push @errors, "We had a problem with the supplied co-ordinates - outside the UK?"; }; } - + my $fh = $q->upload('photo'); if ($fh) { my $err = Page::check_photo($q, $fh); @@ -305,8 +318,9 @@ sub submit_problem { return display_form($q, \@errors, \%field_errors) if (@errors || scalar keys %field_errors); # Short circuit my $areas; - if ($input{easting} && $input{northing}) { - $areas = mySociety::MaPit::call('point', "27700/$input{easting},$input{northing}"); + if (defined $input{latitude} && defined $input{longitude}) { + my $mapit_query = "4326/$input{longitude},$input{latitude}"; + $areas = mySociety::MaPit::call( 'point', $mapit_query ); if ($input{council} =~ /^[\d,]+(\|[\d,]+)?$/) { my $no_details = $1 || ''; my %va = map { $_ => 1 } @$mySociety::VotingArea::council_parent_types; @@ -345,10 +359,10 @@ sub submit_problem { $input{council} = join(',', @valid_councils) . $no_details; } $areas = ',' . join(',', sort keys %$areas) . ','; - } elsif ($input{easting} || $input{northing}) { + } elsif (defined $input{latitude} || defined $input{longitude}) { push(@errors, _('Somehow, you only have one co-ordinate. Please try again.')); } else { - push(@errors, _('You haven\'t specified any sort of co-ordinates. Please try again.')); + push(@errors, _("You haven't specified any sort of co-ordinates. Please try again.")); } my $image; @@ -378,10 +392,10 @@ sub submit_problem { if (my $token = $input{partial}) { my $id = mySociety::AuthToken::retrieve('partial', $token); if ($id) { - dbh()->do("update problem set postcode=?, easting=?, northing=?, title=?, detail=?, + dbh()->do("update problem set postcode=?, latitude=?, longitude=?, title=?, detail=?, name=?, email=?, phone=?, state='confirmed', council=?, used_map='t', anonymous=?, category=?, areas=?, cobrand=?, cobrand_data=?, confirmed=ms_current_timestamp(), - lastupdate=ms_current_timestamp() where id=?", {}, $input{pc}, $input{easting}, $input{northing}, + lastupdate=ms_current_timestamp() where id=?", {}, $input{pc}, $input{latitude}, $input{longitude}, $input{title}, $input{detail}, $input{name}, $input{email}, $input{phone}, $input{council}, $input{anonymous} ? 'f' : 't', $input{category}, $areas, $cobrand, $cobrand_data, $id); @@ -400,11 +414,11 @@ Please <a href="/contact">let us know what went on</a> and we\'ll look into it.' } else { $id = dbh()->selectrow_array("select nextval('problem_id_seq');"); Utils::workaround_pg_bytea("insert into problem - (id, postcode, easting, northing, title, detail, name, + (id, postcode, latitude, longitude, title, detail, name, email, phone, photo, state, council, used_map, anonymous, category, areas, lang, cobrand, cobrand_data) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'unconfirmed', ?, ?, ?, ?, ?, ?, ?, ?)", 10, - $id, $input{pc}, $input{easting}, $input{northing}, $input{title}, + $id, $input{pc}, $input{latitude}, $input{longitude}, $input{title}, $input{detail}, $input{name}, $input{email}, $input{phone}, $image, $input{council}, $used_map, $input{anonymous} ? 'f': 't', $input{category}, $areas, $mySociety::Locale::lang, $cobrand, $cobrand_data); @@ -430,22 +444,29 @@ sub display_form { push @errors, _('There were problems with your report. Please see below.') if (scalar keys %field_errors); my ($pin_x, $pin_y, $pin_tile_x, $pin_tile_y) = (0,0,0,0); - my @vars = qw(title detail name email phone pc easting northing x y skipped council anonymous partial upload_fileid lat lon); - my %input = map { $_ => $q->param($_) || '' } @vars; - my %input_h = map { $_ => $q->param($_) ? ent($q->param($_)) : '' } @vars; + my @vars = qw(title detail name email phone pc latitude longitude x y skipped council anonymous partial upload_fileid); - # Convert lat/lon to easting/northing if given - if ($input{lat}) { - try { - ($input{easting}, $input{northing}) = mySociety::GeoUtil::wgs84_to_national_grid($input{lat}, $input{lon}, 'G'); - $input_h{easting} = $input{easting}; - $input_h{northing} = $input{northing}; - } catch Error::Simple with { - my $e = shift; - push @errors, "We had a problem with the supplied co-ordinates - outside the UK?"; - }; + my %input = (); + my %input_h = (); + + foreach my $key (@vars) { + my $val = $q->param($key); + $input{$key} = defined($val) ? $val : ''; # '0' is valid for longitude + $input_h{$key} = ent( $input{$key} ); } + # Convert lat/lon to easting/northing if given + # if ($input{lat}) { + # try { + # ($input{easting}, $input{northing}) = mySociety::GeoUtil::wgs84_to_national_grid($input{lat}, $input{lon}, 'G'); + # $input_h{easting} = $input{easting}; + # $input_h{northing} = $input{northing}; + # } catch Error::Simple with { + # my $e = shift; + # push @errors, "We had a problem with the supplied co-ordinates - outside the UK?"; + # }; + # } + # Get tile co-ordinates if map clicked ($input{x}) = $input{x} =~ /^(\d+)/; $input{x} ||= 0; ($input{y}) = $input{y} =~ /^(\d+)/; $input{y} ||= 0; @@ -458,28 +479,30 @@ sub display_form { # We need either a map click, an E/N, to be skipping the map, or be filling in a partial form return display_location($q, @errors) unless ($pin_x && $pin_y) - || ($input{easting} && $input{northing}) + || ($input{latitude} && $input{longitude}) || ($input{skipped} && $input{pc}) || ($input{partial} && $input{pc}); # Work out some co-ordinates from whatever we've got - my ($easting, $northing); + my ($latitude, $longitude); if ($input{skipped}) { # Map is being skipped - if ($input{easting} && $input{northing}) { - $easting = $input{easting}; - $northing = $input{northing}; + if ( length $input{latitude} && length $input{longitude} ) { + $latitude = $input{latitude}; + $longitude = $input{longitude}; } else { - my ($e, $n, $error) = FixMyStreet::Geocode::lookup($input{pc}, $q); - $easting = $e; $northing = $n; + my ( $lat, $lon, $error ) = + FixMyStreet::Geocode::lookup( $input{pc}, $q ); + $latitude = $lat; + $longitude = $lon; } } elsif ($pin_x && $pin_y) { # tilma map was clicked on - ($easting, $northing) = FixMyStreet::Map::click_to_os($pin_tile_x, $pin_x, $pin_tile_y, $pin_y); - } elsif ($input{partial} && $input{pc} && !$input{easting} && !$input{northing}) { + ($latitude, $longitude) = FixMyStreet::Map::click_to_wgs84($pin_tile_x, $pin_x, $pin_tile_y, $pin_y); + } elsif ( $input{partial} && $input{pc} && !length $input{latitude} && !length $input{longitude} ) { my $error; try { - ($easting, $northing, $error) = FixMyStreet::Geocode::lookup($input{pc}, $q); + ($latitude, $longitude, $error) = FixMyStreet::Geocode::lookup($input{pc}, $q); } catch Error::Simple with { $error = shift; }; @@ -487,8 +510,8 @@ sub display_form { return front_page($q, $error) if $error; } else { # Normal form submission - $easting = $input_h{easting}; - $northing = $input_h{northing}; + $latitude = $input_h{latitude}; + $longitude = $input_h{longitude}; } # Look up councils and do checks for the point we've got @@ -496,7 +519,7 @@ sub display_form { $parent_types = [qw(DIS LBO MTD UTA LGD COI)] # No CTY if $q->{site} eq 'emptyhomes'; # XXX: I think we want in_gb_locale around the next line, needs testing - my $all_councils = mySociety::MaPit::call('point', "27700/$easting,$northing", type => $parent_types); + my $all_councils = mySociety::MaPit::call('point', "4326/$longitude,$latitude", type => $parent_types); # Let cobrand do a check my ($success, $error_msg) = Cobrand::council_check($cobrand, { all_councils => $all_councils }, $q, 'submit_problem'); @@ -504,10 +527,10 @@ sub display_form { return front_page($q, $error_msg); } - # Ipswich & St Edmundsbury are responsible for everything in their areas, no Suffolk + # Ipswich & St Edmundsbury are responsible for everything in their areas, not Suffolk delete $all_councils->{2241} if $all_councils->{2446} || $all_councils->{2443}; - # Norwich is responsible for everything in its areas, no Norfolk + # Norwich is responsible for everything in its areas, not Norfolk delete $all_councils->{2233} if $all_councils->{2391}; return display_location($q, _('That spot does not appear to be covered by a council. @@ -587,9 +610,9 @@ EOF $type = 1; } $vars{form_start} = FixMyStreet::Map::display_map($q, - easting => $easting, northing => $northing, + latitude => $latitude, longitude => $longitude, type => $type, - pins => [ [ $easting, $northing, 'purple' ] ], + pins => [ [ $latitude, $longitude, 'purple' ] ], ); my $partial_id; if (my $token = $input{partial}) { @@ -683,8 +706,8 @@ photo of the problem if you have one), etc.')); } $vars{text_help} .= ' -<input type="hidden" name="easting" value="' . $easting . '"> -<input type="hidden" name="northing" value="' . $northing . '">'; +<input type="hidden" name="latitude" value="' . $latitude . '"> +<input type="hidden" name="longitude" value="' . $longitude . '">'; if (@errors) { $vars{errors} = '<ul class="error"><li>' . join('</li><li>', @errors) . '</li></ul>'; @@ -763,40 +786,87 @@ EOF return (Page::template_include('report-form', $q, Page::template_root($q), %vars), robots => 'noindex,nofollow'); } +# redirect from osgb +sub redirect_from_osgb_to_wgs84 { + my ($q) = @_; + + my $e = $q->param('e'); + my $n = $q->param('n'); + + my ( $lat, $lon ) = Utils::convert_en_to_latlon_truncated( $e, $n ); + + my $lat_lon_url = NewURL( + $q, + -retain => 1, + e => undef, + n => undef, + lat => $lat, + lon => $lon + ); + + print $q->redirect( + -location => $lat_lon_url, + -status => 301, # permanent + ); + + return ''; +} + sub display_location { my ($q, @errors) = @_; my $cobrand = Page::get_cobrand($q); - my @vars = qw(pc x y e n all_pins no_pins); - my %input = map { $_ => $q->param($_) || '' } @vars; - my %input_h = map { $_ => $q->param($_) ? ent($q->param($_)) : '' } @vars; + my @vars = qw(pc x y lat lon all_pins no_pins); - (my $easting) = $input{e} =~ /^(\d+)/; $easting ||= 0; - (my $northing) = $input{n} =~ /^(\d+)/; $northing ||= 0; + my %input = (); + my %input_h = (); + + foreach my $key (@vars) { + my $val = $q->param($key); + $input{$key} = defined($val) ? $val : ''; # '0' is valid for longitude + $input_h{$key} = ent( $input{$key} ); + } + + my $latitude = $input{lat}; + my $longitude = $input{lon}; # X/Y referring to tiles old-school (my $x) = $input{x} =~ /^(\d+)/; $x ||= 0; (my $y) = $input{y} =~ /^(\d+)/; $y ||= 0; - return front_page($q, @errors) unless $x || $y || $input{pc} || $easting || $northing; - if ($x && $y) { + return front_page( $q, @errors ) + unless ( $x && $y ) + || $input{pc} + || ( defined $latitude && defined $longitude ); + + if ( $x && $y ) { + # Convert the tile co-ordinates to real ones. - $easting = FixMyStreet::Map::tile_to_os($x); - $northing = FixMyStreet::Map::tile_to_os($y); - } elsif ($easting && $northing) { + ( $latitude, $longitude ) = + FixMyStreet::Map::tile_xy_to_wgs84( $x, $y ); + } + elsif ( $latitude && $longitude ) { + # Don't need to do anything - } else { + } + else { my $error; try { - ($easting, $northing, $error) = FixMyStreet::Geocode::lookup($input{pc}, $q); - } catch Error::Simple with { + ( $latitude, $longitude, $error ) = + FixMyStreet::Geocode::lookup( $input{pc}, $q ); + + debug 'Looked up postcode "%s": lat: "%s", lon: "%s", error: "%s"', + $input{pc}, $latitude, $longitude, $error; + } + catch Error::Simple with { $error = shift; }; - return FixMyStreet::Geocode::list_choices($error, '/', $q) if (ref($error) eq 'ARRAY'); - return front_page($q, $error) if $error; + return FixMyStreet::Geocode::list_choices( $error, '/', $q ) + if ( ref($error) eq 'ARRAY' ); + return front_page( $q, $error ) if $error; } # Check this location is okay to be displayed for the cobrand - my ($success, $error_msg) = Cobrand::council_check($cobrand, { e => $easting, n => $northing }, $q, 'display_location'); + my ($success, $error_msg) = Cobrand::council_check($cobrand, { lat => $latitude, lon => $longitude }, $q, 'display_location'); return front_page($q, $error_msg) unless $success; # Deal with pin hiding/age @@ -810,10 +880,10 @@ sub display_location { $interval = '6 months'; } - my ($on_map_all, $on_map, $around_map, $dist) = FixMyStreet::Map::map_features($q, $easting, $northing, $interval); + my ($on_map_all, $on_map, $around_map, $dist) = FixMyStreet::Map::map_features($q, $latitude, $longitude, $interval); my @pins; foreach (@$on_map_all) { - push @pins, [ $_->{easting}, $_->{northing}, $_->{state} eq 'fixed' ? 'green' : 'red' ]; + push @pins, [ $_->{latitude}, $_->{longitude}, $_->{state} eq 'fixed' ? 'green' : 'red' ]; } my $on_list = ''; foreach (@$on_map) { @@ -839,7 +909,7 @@ sub display_location { $around_list .= $dist . 'km)</small>'; $around_list .= ' <small>' . _('(fixed)') . '</small>' if $_->{state} eq 'fixed'; $around_list .= '</li>'; - push @pins, [ $_->{easting}, $_->{northing}, $_->{state} eq 'fixed' ? 'green' : 'red' ]; + push @pins, [ $_->{latitude}, $_->{longitude}, $_->{state} eq 'fixed' ? 'green' : 'red' ]; } $around_list = $q->li(_('No problems found.')) unless $around_list; @@ -856,21 +926,27 @@ sub display_location { my $url_skip = NewURL($q, -retain=>1, pc => undef, x => undef, 'y' => undef, - easting => $easting, northing => $northing, + latitude => $latitude, longitude => $longitude, 'submit_map'=>1, skipped=>1 ); my $pc_h = ent($q->param('pc') || ''); + + # truncate the lat,lon for nicer rss urls + my ( $short_lat, $short_lon ) = + map { Utils::truncate_coordinate($_) } # + ( $latitude, $longitude ); + my %vars = ( 'map' => FixMyStreet::Map::display_map($q, - easting => $easting, northing => $northing, + latitude => $latitude, longitude => $longitude, type => 1, pins => \@pins, post => $map_links ), map_end => FixMyStreet::Map::display_map_end(1), url_home => Cobrand::url($cobrand, '/', $q), - url_rss => Cobrand::url($cobrand, NewURL($q, -retain => 1, -url=> "/rss/n/$easting,$northing", pc => undef, x => undef, 'y' => undef), $q), - url_email => Cobrand::url($cobrand, NewURL($q, -retain => 1, pc => undef, -url=>'/alert', e=>$easting, 'n'=>$northing, feed=>"local:$easting:$northing"), $q), + url_rss => Cobrand::url($cobrand, NewURL($q, -retain => 1, -url=> "/rss/l/$short_lat,$short_lon", pc => undef, x => undef, y => undef, lat => undef, lon => undef ), $q), + url_email => Cobrand::url($cobrand, NewURL($q, -retain => 1, pc => undef, lat => $short_lat, lon => $short_lon, -url=>'/alert', feed=>"local:$short_lat:$short_lon"), $q), url_skip => $url_skip, email_me => _('Email me new local problems'), rss_alt => _('RSS feed'), @@ -890,7 +966,7 @@ sub display_location { ); my %params = ( - rss => [ _('Recent local problems, FixMyStreet'), "/rss/n/$easting,$northing" ], + rss => [ _('Recent local problems, FixMyStreet'), "/rss/l/$short_lat,$short_lon" ], robots => 'noindex,nofollow', ); @@ -931,9 +1007,12 @@ sub display_problem { my $extra_data = Cobrand::extra_data($cobrand, $q); my $google_link = Cobrand::base_url_for_emails($cobrand, $extra_data) . '/report/' . $problem->{id}; - my ($lat, $lon) = mySociety::GeoUtil::national_grid_to_wgs84($problem->{easting}, $problem->{northing}, 'G'); - my $map_links = "<p id='sub_map_links'><a href=\"http://maps.google.co.uk/maps?output=embed&z=16&q=" - . URI::Escape::uri_escape_utf8($problem->{title} . ' - ' . $google_link) . "\@$lat,$lon\">View on Google Maps</a></p>"; + + my $map_links = + "<p id='sub_map_links'>" + . "<a href=\"http://maps.google.co.uk/maps?output=embed&z=16&q=" + . URI::Escape::uri_escape_utf8( $problem->{title} . ' - ' . $google_link ) + . "\@$problem->{latitude},$problem->{longitude}\">View on Google Maps</a></p>"; my $banner; if ($q->{site} ne 'emptyhomes' && $problem->{state} eq 'confirmed' && $problem->{duration} > 8*7*24*60*60) { @@ -945,7 +1024,7 @@ sub display_problem { my $contact_url = Cobrand::url($cobrand, NewURL($q, -retain => 1, pc => undef, x => undef, 'y' => undef, -url=>'/contact?id=' . $input{id}), $q); my $back = Cobrand::url($cobrand, NewURL($q, -url => '/', - 'e' => int($problem->{easting}), 'n' => int($problem->{northing}), + lat => $problem->{latitude}, lon => $problem->{longitude}, -retain => 1, pc => undef, x => undef, 'y' => undef, id => undef ), $q); my $fixed = ($input{fixed}) ? ' checked' : ''; @@ -953,9 +1032,9 @@ sub display_problem { my %vars = ( banner => $banner, map_start => FixMyStreet::Map::display_map($q, - easting => $problem->{easting}, northing => $problem->{northing}, + latitude => $problem->{latitude}, longitude => $problem->{longitude}, type => 0, - pins => [ [ $problem->{easting}, $problem->{northing}, 'blue' ] ], + pins => [ [ $problem->{latitude}, $problem->{longitude}, 'blue' ] ], post => $map_links ), map_end => FixMyStreet::Map::display_map_end(0), diff --git a/web/questionnaire.cgi b/web/questionnaire.cgi index 33e823560..cb5e553d3 100755 --- a/web/questionnaire.cgi +++ b/web/questionnaire.cgi @@ -220,9 +220,9 @@ sub display_questionnaire { my %vars = ( input_h => \%input_h, map_start => FixMyStreet::Map::display_map($q, - easting => $problem->{easting}, northing => $problem->{northing}, + latitude => $problem->{latitude}, longitude => $problem->{longitude}, pins => [ - [ $problem->{easting}, $problem->{northing}, $problem->{state} eq 'fixed'?'green':'red' ], + [ $problem->{latitude}, $problem->{longitude}, $problem->{state} eq 'fixed'?'green':'red' ], ], pre => $problem_text, post => $updates ), diff --git a/web/rss.cgi b/web/rss.cgi index 8885af249..9a9b4ce18 100755 --- a/web/rss.cgi +++ b/web/rss.cgi @@ -17,6 +17,7 @@ use FixMyStreet::Geocode; use mySociety::MaPit; use mySociety::GeoUtil; use mySociety::Gaze; +use Utils; sub main { my $q = shift; @@ -83,35 +84,39 @@ sub rss_local_problems { my $cobrand = Page::get_cobrand($q); my $base = Cobrand::base_url($cobrand); - if ($lat) { # In the UK, it'll never be 0 :) - ($e, $n) = mySociety::GeoUtil::wgs84_to_national_grid($lat, $lon, 'G'); - $e = int($e + 0.5); - $n = int($n + 0.5); - print $q->redirect(-location => "$base/rss/n/$e,$n$d_str$state_qs"); - return ''; - } elsif ($x && $y) { + if ($x && $y) { # 5000/31 as initial scale factor for these RSS feeds, now variable so redirect. $e = int( ($x * 5000/31) + 0.5 ); $n = int( ($y * 5000/31) + 0.5 ); - print $q->redirect(-location => "$base/rss/n/$e,$n$d_str$state_qs"); + ($lat, $lon) = Utils::convert_en_to_latlon($e, $n); + print $q->redirect(-location => "$base/rss/l/$lat,$lon$d_str$state_qs"); return ''; } elsif ($e && $n) { - ($lat, $lon) = mySociety::GeoUtil::national_grid_to_wgs84($e, $n, 'G'); + ($lat, $lon) = Utils::convert_en_to_latlon($e, $n); + print $q->redirect(-location => "$base/rss/l/$lat,$lon$d_str$state_qs"); + return ''; } elsif ($pc) { my $error; try { - ($e, $n, $error) = FixMyStreet::Geocode::lookup($pc, $q); + ($lat, $lon, $error) = FixMyStreet::Geocode::lookup($pc, $q); } catch Error::Simple with { $error = shift; }; unless ($error) { - print $q->redirect(-location => "$base/rss/n/$e,$n$d_str$state_qs"); + print $q->redirect(-location => "$base/rss/l/$lat,$lon$d_str$state_qs"); } return ''; + } elsif ( $lat || $lon ) { + # pass through } else { die "Missing E/N, x/y, lat/lon, or postcode parameter in RSS feed"; } - my $qs = '?e=' . int($e) . ';n=' . int($n); + + # truncate the lat,lon for nicer urls + ( $lat, $lon ) = map { Utils::truncate_coordinate($_) } ( $lat, $lon ); + + my $qs = "?lat=$lat;lon/=$lon"; + if ($d) { $qs .= ";d=$d"; $d = 100 if $d > 100; @@ -122,9 +127,9 @@ sub rss_local_problems { my $xsl = Cobrand::feed_xsl($cobrand); if ($state eq 'all') { - return FixMyStreet::Alert::generate_rss('local_problems', $xsl, $qs, [$e, $n, $d], undef, $cobrand, $q); + return FixMyStreet::Alert::generate_rss('local_problems', $xsl, $qs, [$lat, $lon, $d], undef, $cobrand, $q); } else { - return FixMyStreet::Alert::generate_rss('local_problems_state', $xsl, $qs, [$e, $n, $d, $state], undef, $cobrand, $q); + return FixMyStreet::Alert::generate_rss('local_problems_state', $xsl, $qs, [$lat, $lon, $d, $state], undef, $cobrand, $q); } } |