diff options
51 files changed, 2978 insertions, 983 deletions
diff --git a/bin/gettext-extract b/bin/gettext-extract index fb3406984..b354c11f2 100755 --- a/bin/gettext-extract +++ b/bin/gettext-extract @@ -40,7 +40,7 @@ PO=locale/FixMyStreet.po rm -f $PO # Extract from Perl -xgettext --add-comments=TRANS --language=Perl --keyword=_ --keyword=nget:1,2 --from-code=utf-8 -o $PO commonlib/perllib/mySociety/*.pm perllib/*.pm web/*.cgi bin/send-* db/alert_types_eha.pl +xgettext --add-comments=TRANS --language=Perl --keyword=_ --keyword=nget:1,2 --from-code=utf-8 -o $PO perllib/*.pm perllib/FixMyStreet/*.pm web/*.cgi bin/send-* db/alert_types_eha.pl # Fix headers TEMP=`tempfile` diff --git a/bin/send-alerts b/bin/send-alerts index 57eeb16ae..c52af4059 100755 --- a/bin/send-alerts +++ b/bin/send-alerts @@ -21,7 +21,7 @@ use CronFns; use mySociety::Config; use mySociety::DBHandle qw(dbh); -use mySociety::Alert; +use FixMyStreet::Alert; BEGIN { mySociety::Config::set_file("$FindBin::Bin/../conf/general"); @@ -37,5 +37,5 @@ BEGIN { my $site = CronFns::site(mySociety::Config::get('BASE_URL')); CronFns::language($site); my $testing_email = mySociety::Config::get('TESTING_EMAIL'); -mySociety::Alert::email_alerts($testing_email); +FixMyStreet::Alert::email_alerts($testing_email); diff --git a/bin/test-run b/bin/test-run index 3e5f76afa..15f9c3ed2 100755 --- a/bin/test-run +++ b/bin/test-run @@ -377,8 +377,8 @@ sub do_alert { # sign up for alerts in an area my $postcode = 'EH1 2NG'; - my $x = 2015; my $e = 325000; - my $y = 4175; my $n = 673548; + my $x = 2015; my $e = 325066; + my $y = 4175; my $n = 673533; my $messages = english_fms_messages(); submit_postcode('', $postcode, 'Problems in this area'); $wth->browser_follow_link(text => 'Email me new local problems'); @@ -417,8 +417,8 @@ sub do_alert { sub do_eha_alert { # sign up for alerts in an area my $postcode = 'EH1 2NG'; - my $x = 2015; my $e = 325000; - my $y = 4175; my $n = 673548; + my $x = 2015; my $e = 325066; + my $y = 4175; my $n = 673533; my @texts = ('Eiddo gwag yn yr ardal hon', 'Adrodd am eiddo gwag', 'Nawr, gwiriwch eich e-bost', diff --git a/commonlib b/commonlib -Subproject 13e2591319b761789a41a302bd2de54d454776e +Subproject e0ffea0186a31514b0a05efd30fc76e83f68d3d diff --git a/conf/general-example b/conf/general-example index f8c71c846..0a5c9e44d 100644 --- a/conf/general-example +++ b/conf/general-example @@ -49,12 +49,7 @@ define('OPTION_GEO_CACHE', '/cache/'); define('OPTION_GOOGLE_MAPS_API_KEY', ''); define('OPTION_MAPIT_URL', 'http://services.mysociety.org/mapit'); -define('OPTION_TILES_TYPE', '10k-full-london'); -define('OPTION_TILES_TIFF_SIZE_METRES', 5000); -define('OPTION_TILES_TIFF_SIZE_PIXELS', 7874); -define('OPTION_TILES_WIDTH', 254); -define('OPTION_TILES_URL', 'http://tilma.mysociety.org/tileserver/' . OPTION_TILES_TYPE . '/'); - +define('OPTION_MAP_TYPE', 'Tilma::Original::1_10k'); define('OPTION_EVEL_URL', 'http://services.mysociety.org/evel'); define('OPTION_GAZE_URL', 'http://gaze.mysociety.org/gaze'); diff --git a/db/alert_types.sql b/db/alert_types.sql index d605d8e65..82a8c71f8 100644 --- a/db/alert_types.sql +++ b/db/alert_types.sql @@ -42,6 +42,17 @@ values ('local_problems', '', '', 'problem_find_nearby(?, ?, ?) as nearby,problem', 'nearby.problem_id = problem.id and problem.state in (''confirmed'', ''fixed'')', 'created desc', '{{title}}, {{confirmed}}', '/report/{{id}}', '{{detail}}', 'alert-problem-nearby'); +-- New problems around a location +insert into alert_type +(ref, head_sql_query, head_table, + head_title, head_link, head_description, + item_table, item_where, item_order, + item_title, item_link, item_description, template) +values ('local_problems_state', '', '', + 'New local problems on FixMyStreet', '/', 'The latest local problems reported by users', + 'problem_find_nearby(?, ?, ?) as nearby,problem', 'nearby.problem_id = problem.id and problem.state in (?)', 'created desc', + '{{title}}, {{confirmed}}', '/report/{{id}}', '{{detail}}', 'alert-problem-nearby'); + -- New problems sent to a particular council insert into alert_type (ref, head_sql_query, head_table, diff --git a/perllib/Cobrand.pm b/perllib/Cobrand.pm index 4433858bb..328445fcc 100644 --- a/perllib/Cobrand.pm +++ b/perllib/Cobrand.pm @@ -75,8 +75,6 @@ my %fns = ( # Given a QUERY, return a hash of extra params to be included in # any URLs in links produced on the page returned by that query. 'extra_params' => { default => "''" }, - # Returns a boolean indicating whether the map watermark should be displayed - 'show_watermark' => { default => '1' }, # Returns any extra text to be displayed with a PROBLEM. 'extra_problem_meta_text' => { default => "''" }, # Returns any extra text to be displayed with an UPDATE. diff --git a/perllib/Cobrands/Barnet/Util.pm b/perllib/Cobrands/Barnet/Util.pm index 3cc60117f..32973c10c 100644 --- a/perllib/Cobrands/Barnet/Util.pm +++ b/perllib/Cobrands/Barnet/Util.pm @@ -3,18 +3,13 @@ # Util.pm: # Barnet cobranding for FixMyStreet. # -# # Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved. # Email: matthew@mysociety.org. WWW: http://www.mysociety.org -# -# $Id: Util.pm,v 1.9 2009-12-22 13:04:05 matthew Exp $ package Cobrands::Barnet::Util; -use Standard; use strict; use Carp; use URI::Escape; -use mySociety::Web qw(ent); use mySociety::VotingArea; sub new { @@ -86,7 +81,7 @@ sub council_check { $url .= 'alert' if $context eq 'alert'; $url .= '?pc=' . URI::Escape::uri_escape_utf8($q->param('pc')) if $q->param('pc'); my $error_msg = "That location is not covered by Barnet. -Please visit <a href='$url'>the main FixMyStreet site</a>."; +Please visit <a href=\"$url\">the main FixMyStreet site</a>."; return (0, $error_msg); } diff --git a/perllib/Cobrands/Emptyhomes/Util.pm b/perllib/Cobrands/Emptyhomes/Util.pm index f562a849b..acb870695 100644 --- a/perllib/Cobrands/Emptyhomes/Util.pm +++ b/perllib/Cobrands/Emptyhomes/Util.pm @@ -3,19 +3,14 @@ # Util.pm: # Emptyhomes Cobranding for FixMyStreet. # -# # Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved. # Email: louise@mysociety.org. WWW: http://www.mysociety.org -# -# $Id: Util.pm,v 1.8 2009-12-22 11:17:26 matthew Exp $ package Cobrands::Emptyhomes::Util; -use Standard; use strict; use Carp; -use mySociety::Web qw(ent); -sub new{ +sub new { my $class = shift; return bless {}, $class; } @@ -44,7 +39,7 @@ Set the language and text domain for the site based on the query and host. =cut -sub set_lang_and_domain{ +sub set_lang_and_domain { my ($self, $lang, $unicode) = @_; mySociety::Locale::negotiate_language('en-gb,English,en_GB|cy,Cymraeg,cy_GB', $lang); mySociety::Locale::gettext_domain('FixMyStreet-EmptyHomes', $unicode); diff --git a/perllib/FixMyStreet/Alert.pm b/perllib/FixMyStreet/Alert.pm new file mode 100644 index 000000000..9996f03c8 --- /dev/null +++ b/perllib/FixMyStreet/Alert.pm @@ -0,0 +1,339 @@ +#!/usr/bin/perl -w +# +# FixMyStreet::Alert.pm +# Alerts by email or RSS. +# +# Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ +# +# $Id: Alert.pm,v 1.71 2010-01-06 16:50:27 louise Exp $ + +package FixMyStreet::Alert::Error; + +use Error qw(:try); + +@FixMyStreet::Alert::Error::ISA = qw(Error::Simple); + +package FixMyStreet::Alert; + +use strict; +use Error qw(:try); +use File::Slurp; +use FindBin; +use POSIX qw(strftime); +use XML::RSS; + +use Cobrand; +use mySociety::AuthToken; +use mySociety::Config; +use mySociety::DBHandle qw(dbh); +use mySociety::Email; +use mySociety::EmailUtil; +use mySociety::Gaze; +use mySociety::GeoUtil; +use mySociety::Locale; +use mySociety::MaPit; +use mySociety::Random qw(random_bytes); +use mySociety::Sundries qw(ordinal); +use mySociety::Web qw(ent); + +# Add a new alert +sub create ($$$$;@) { + my ($email, $alert_type, $cobrand, $cobrand_data, @params) = @_; + my $already = 0; + if (0==@params) { + ($already) = dbh()->selectrow_array('select id from alert where alert_type=? and email=? limit 1', + {}, $alert_type, $email); + } elsif (1==@params) { + ($already) = dbh()->selectrow_array('select id from alert where alert_type=? and email=? and parameter=? limit 1', + {}, $alert_type, $email, @params); + } elsif (2==@params) { + ($already) = dbh()->selectrow_array('select id from alert where alert_type=? and email=? and parameter=? and parameter2=? limit 1', + {}, $alert_type, $email, @params); + } + return $already if $already; + + my $id = dbh()->selectrow_array("select nextval('alert_id_seq');"); + my $lang = $mySociety::Locale::lang; + if (0==@params) { + dbh()->do('insert into alert (id, alert_type, email, lang, cobrand, cobrand_data) + values (?, ?, ?, ?, ?, ?)', {}, $id, $alert_type, $email, $lang, $cobrand, $cobrand_data); + } elsif (1==@params) { + dbh()->do('insert into alert (id, alert_type, parameter, email, lang, cobrand, cobrand_data) + values (?, ?, ?, ?, ?, ?, ?)', {}, $id, $alert_type, @params, $email, $lang, $cobrand, $cobrand_data); + } elsif (2==@params) { + dbh()->do('insert into alert (id, alert_type, parameter, parameter2, email, lang, cobrand, cobrand_data) + values (?, ?, ?, ?, ?, ?, ?, ?)', {}, $id, $alert_type, @params, $email, $lang, $cobrand, $cobrand_data); + } + dbh()->commit(); + return $id; +} + +sub confirm ($) { + my $id = shift; + dbh()->do("update alert set confirmed=1, whendisabled=null where id=?", {}, $id); + dbh()->commit(); +} + +# Delete an alert +sub delete ($) { + my $id = shift; + dbh()->do('update alert set whendisabled = ms_current_timestamp() where id = ?', {}, $id); + dbh()->commit(); +} + +# This makes load of assumptions, but still should be useful +# +# Child must have confirmed, id, email, state(!) columns +# If parent/child, child table must also have name and text +# and foreign key to parent must be PARENT_id + +sub email_alerts ($) { + my ($testing_email) = @_; + my $url; + my $q = dbh()->prepare("select * from alert_type where ref not like 'local_problems%'"); + $q->execute(); + my $testing_email_clause = ''; + while (my $alert_type = $q->fetchrow_hashref) { + my $ref = $alert_type->{ref}; + my $head_table = $alert_type->{head_table}; + my $item_table = $alert_type->{item_table}; + my $testing_email_clause = "and $item_table.email <> '$testing_email'" if $testing_email; + my $query = 'select alert.id as alert_id, alert.email as alert_email, alert.lang as alert_lang, alert.cobrand as alert_cobrand, + alert.cobrand_data as alert_cobrand_data, alert.parameter as alert_parameter, alert.parameter2 as alert_parameter2, '; + if ($head_table) { + $query .= " + $item_table.id as item_id, $item_table.name as item_name, $item_table.text as item_text, + $head_table.* + from alert + inner join $item_table on alert.parameter::integer = $item_table.${head_table}_id + inner join $head_table on alert.parameter::integer = $head_table.id"; + } else { + $query .= " $item_table.*, + $item_table.id as item_id + from alert, $item_table"; + } + $query .= " + where alert_type='$ref' and whendisabled is null and $item_table.confirmed >= whensubscribed + and $item_table.confirmed >= ms_current_timestamp() - '7 days'::interval + and (select whenqueued from alert_sent where alert_sent.alert_id = alert.id and alert_sent.parameter::integer = $item_table.id) is null + and $item_table.email <> alert.email + $testing_email_clause + and $alert_type->{item_where} + and alert.confirmed = 1 + order by alert.id, $item_table.confirmed"; + # XXX Ugh - needs work + $query =~ s/\?/alert.parameter/ if ($query =~ /\?/); + $query =~ s/\?/alert.parameter2/ if ($query =~ /\?/); + $query = dbh()->prepare($query); + $query->execute(); + my $last_alert_id; + my %data = ( template => $alert_type->{template}, data => '' ); + while (my $row = $query->fetchrow_hashref) { + # Cobranded and non-cobranded messages can share a database. In this case, the conf file + # should specify a vhost to send the reports for each cobrand, so that they don't get sent + # more than once if there are multiple vhosts running off the same database. The email_host + # call checks if this is the host that sends mail for this cobrand. + next unless (Cobrand::email_host($row->{alert_cobrand})); + dbh()->do('insert into alert_sent (alert_id, parameter) values (?,?)', {}, $row->{alert_id}, $row->{item_id}); + if ($last_alert_id && $last_alert_id != $row->{alert_id}) { + _send_aggregated_alert_email(%data); + %data = ( template => $alert_type->{template}, data => '' ); + } + + $url = Cobrand::base_url_for_emails($row->{alert_cobrand}, $row->{alert_cobrand_data}); + if ($row->{item_text}) { + $data{problem_url} = $url . "/report/" . $row->{id}; + $data{data} .= $row->{item_name} . ' : ' if $row->{item_name}; + $data{data} .= $row->{item_text} . "\n\n------\n\n"; + } else { + $data{data} .= $url . "/report/" . $row->{id} . " - $row->{title}\n\n"; + } + if (!$data{alert_email}) { + %data = (%data, %$row); + if ($ref eq 'area_problems' || $ref eq 'council_problems' || $ref eq 'ward_problems') { + my $va_info = mySociety::MaPit::get_voting_area_info($row->{alert_parameter}); + $data{area_name} = $va_info->{name}; + } + if ($ref eq 'ward_problems') { + my $va_info = mySociety::MaPit::get_voting_area_info($row->{alert_parameter2}); + $data{ward_name} = $va_info->{name}; + } + } + $data{cobrand} = $row->{alert_cobrand}; + $data{cobrand_data} = $row->{alert_cobrand_data}; + $data{lang} = $row->{alert_lang}; + $last_alert_id = $row->{alert_id}; + } + if ($last_alert_id) { + _send_aggregated_alert_email(%data); + } + } + + # Nearby done separately as the table contains the parameters + my $template = dbh()->selectrow_array("select template from alert_type where ref = 'local_problems'"); + my $query = "select * from alert where alert_type='local_problems' and whendisabled is null and confirmed=1 order by id"; + $query = dbh()->prepare($query); + $query->execute(); + while (my $alert = $query->fetchrow_hashref) { + next unless (Cobrand::email_host($alert->{cobrand})); + my $e = $alert->{parameter}; + my $n = $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); + $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} ); + my $q = "select * from problem_find_nearby(?, ?, ?) as nearby, problem + where nearby.problem_id = problem.id and problem.state in ('confirmed', 'fixed') + and problem.confirmed >= ? and problem.confirmed >= ms_current_timestamp() - '7 days'::interval + and (select whenqueued from alert_sent where alert_sent.alert_id = ? and alert_sent.parameter::integer = problem.id) is null + and problem.email <> ? + $testing_email_clause + $site_restriction + order by confirmed desc"; + $q = dbh()->prepare($q); + $q->execute($e, $n, $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"; + } + _send_aggregated_alert_email(%data) if $data{data}; + } +} + +sub _send_aggregated_alert_email(%) { + my %data = @_; + Cobrand::set_lang_and_domain($data{cobrand}, $data{lang}, 1); + + $data{unsubscribe_url} = Cobrand::base_url_for_emails($data{cobrand}, $data{cobrand_data}) . '/A/' + . mySociety::AuthToken::store('alert', { id => $data{alert_id}, type => 'unsubscribe', email => $data{alert_email} } ); + my $template = "$FindBin::Bin/../templates/emails/$data{template}"; + if ($data{cobrand}) { + my $template_cobrand = "$FindBin::Bin/../templates/emails/$data{cobrand}/$data{template}"; + $template = $template_cobrand if -e $template_cobrand; + } + $template = File::Slurp::read_file($template); + my $sender = Cobrand::contact_email($data{cobrand}); + my $sender_name = Cobrand::contact_name($data{cobrand}); + (my $from = $sender) =~ s/team/fms-DO-NOT-REPLY/; # XXX + my $email = mySociety::Email::construct_email({ + _template_ => _($template), + _parameters_ => \%data, + From => [ $from, _($sender_name) ], + To => $data{alert_email}, + 'Message-ID' => sprintf('<alert-%s-%s@mysociety.org>', time(), unpack('h*', random_bytes(5, 1))), + }); + + my $result = mySociety::EmailUtil::send_email($email, $sender, $data{alert_email}); + if ($result == mySociety::EmailUtil::EMAIL_SUCCESS) { + dbh()->commit(); + } else { + dbh()->rollback(); + throw FixMyStreet::Alert::Error("Failed to send alert $data{alert_id}!"); + } +} + +sub generate_rss ($$$;$$$$) { + my ($type, $xsl, $qs, $db_params, $title_params, $cobrand, $http_q) = @_; + $db_params ||= []; + my $url = Cobrand::base_url($cobrand); + my $cobrand_data = Cobrand::extra_data($cobrand, $http_q); + my $q = dbh()->prepare('select * from alert_type where ref=?'); + $q->execute($type); + my $alert_type = $q->fetchrow_hashref; + my ($site_restriction, $site_id) = Cobrand::site_restriction($cobrand, $cobrand_data); + throw FixMyStreet::Alert::Error('Unknown alert type') unless $alert_type; + + # Do our own encoding + + my $rss = new XML::RSS( version => '2.0', encoding => 'UTF-8', + stylesheet=> $xsl, encode_output => undef ); + $rss->add_module(prefix=>'georss', uri=>'http://www.georss.org/georss'); + + # XXX: Not generic + # Only apply a site restriction if the alert uses the problem table + $site_restriction = '' unless $alert_type->{item_table} eq 'problem'; + my $query = 'select * from ' . $alert_type->{item_table} . ' where ' + . ($alert_type->{head_table} ? $alert_type->{head_table}.'_id=? and ' : '') + . $alert_type->{item_where} . $site_restriction . ' order by ' + . $alert_type->{item_order}; + $query .= ' limit 20' unless $type =~ /^all/; + $q = dbh()->prepare($query); + if ($query =~ /\?/) { + throw FixMyStreet::Alert::Error('Missing parameter') unless @$db_params; + $q->execute(@$db_params); + } else { + $q->execute(); + } + + my @months = ('', 'January','February','March','April','May','June', + 'July','August','September','October','November','December'); + while (my $row = $q->fetchrow_hashref) { + # XXX: How to do this properly? name might be null in comment table, hence needing this + my $pubDate; + $row->{name} ||= 'anonymous'; + # And we want pretty dates... :-/ + if ($row->{confirmed}) { + $row->{confirmed} =~ /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/; + $pubDate = mySociety::Locale::in_gb_locale { + strftime("%a, %d %b %Y %H:%M:%S %z", $6, $5, $4, $3, $2-1, $1-1900, -1, -1, 0) + }; + $row->{confirmed} = ordinal($3+0) . ' ' . $months[$2]; + } + + (my $title = _($alert_type->{item_title})) =~ s/{{(.*?)}}/$row->{$1}/g; + (my $link = $alert_type->{item_link}) =~ s/{{(.*?)}}/$row->{$1}/g; + (my $desc = _($alert_type->{item_description})) =~ s/{{(.*?)}}/$row->{$1}/g; + my $cobrand_url = Cobrand::url($cobrand, $url . $link, $http_q); + my %item = ( + title => ent($title), + link => $cobrand_url, + guid => $cobrand_url, + description => ent(ent($desc)) # Yes, double-encoded, really. + ); + $item{pubDate} = $pubDate if $pubDate; + + # XXX: Not-very-generic extensions, at all + my $display_photos = Cobrand::allow_photo_display($cobrand); + if ($display_photos && $row->{photo}) { + $item{description} .= ent("\n<br><img src=\"". Cobrand::url($cobrand, $url, $http_q) . "/photo?id=$row->{id}\">"); + } + $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" }; + } + $rss->add_item( %item ); + } + + my $row = {}; + if ($alert_type->{head_sql_query}) { + $q = dbh()->prepare($alert_type->{head_sql_query}); + if ($alert_type->{head_sql_query} =~ /\?/) { + $q->execute(@$db_params); + } else { + $q->execute(); + } + $row = $q->fetchrow_hashref; + } + foreach (keys %$title_params) { + $row->{$_} = $title_params->{$_}; + } + (my $title = _($alert_type->{head_title})) =~ s/{{(.*?)}}/$row->{$1}/g; + (my $link = $alert_type->{head_link}) =~ s/{{(.*?)}}/$row->{$1}/g; + (my $desc = _($alert_type->{head_description})) =~ s/{{(.*?)}}/$row->{$1}/g; + $rss->channel( + title => ent($title), link => "$url$link$qs", description => ent($desc), + language => 'en-gb' + ); + + my $out = $rss->as_string; + my $uri = Cobrand::url($cobrand, $ENV{SCRIPT_URI}, $http_q); + $out =~ s{<link>(.*?)</link>}{"<link>" . Cobrand::url($cobrand, $1, $http_q) . "</link><uri>$uri</uri>"}e; + + return $out; +} diff --git a/perllib/FixMyStreet/Geocode.pm b/perllib/FixMyStreet/Geocode.pm new file mode 100644 index 000000000..9e89b4f7b --- /dev/null +++ b/perllib/FixMyStreet/Geocode.pm @@ -0,0 +1,156 @@ +#!/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 ($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}; + } + } else { + ($easting, $northing, $error) = FixMyStreet::Geocode::string($s, $q); + } + return ($easting, $northing, $error); +} + +sub geocoded_string_coordinates { + my ($js, $q) = @_; + my ($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'); + } 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); +} + +# 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, $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 { + ($easting, $northing, $error) = geocoded_string_coordinates($js, $q); + } + return ($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/FixMyStreet/Map.pm b/perllib/FixMyStreet/Map.pm new file mode 100644 index 000000000..12ecf78fe --- /dev/null +++ b/perllib/FixMyStreet/Map.pm @@ -0,0 +1,114 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map +# Adding the ability to have different maps on FixMyStreet. +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map; + +use strict; + +use Problems; +use Cobrand; +use mySociety::Config; +use mySociety::Gaze; +use mySociety::GeoUtil; +use mySociety::Locale; +use mySociety::Web qw(ent NewURL); + +# 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 $class = "FixMyStreet::Map::$type"; + eval "use $class"; +} + +sub header { + 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 $encoding = ''; + $encoding = ' enctype="multipart/form-data"' if $type==2; + my $pc = $q->param('pc') || ''; + my $pc_enc = ent($pc); + return <<EOF; +<form action="$form_action" method="post" name="mapForm" id="mapForm"$encoding> +<input type="hidden" name="submit_map" value="1"> +<input type="hidden" name="pc" value="$pc_enc"> +$cobrand_form_elements +EOF +} + +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 $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 = 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); + + return ($around_map, $around_map_list, $nearby, $dist); +} + +sub compass ($$$) { + 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); + } + } + my $recentre = NewURL($q); + my $host = Page::base_url_with_lang($q, undef); + return <<EOF; +<table cellpadding="0" cellspacing="0" border="0" id="compass"> +<tr valign="bottom"> +<td align="right"><a rel="nofollow" href="${compass[$x-1][$y+1]}"><img src="$host/i/arrow-northwest.gif" alt="NW" width=11 height=11></a></td> +<td align="center"><a rel="nofollow" href="${compass[$x][$y+1]}"><img src="$host/i/arrow-north.gif" vspace="3" alt="N" width=13 height=11></a></td> +<td><a rel="nofollow" href="${compass[$x+1][$y+1]}"><img src="$host/i/arrow-northeast.gif" alt="NE" width=11 height=11></a></td> +</tr> +<tr> +<td><a rel="nofollow" href="${compass[$x-1][$y]}"><img src="$host/i/arrow-west.gif" hspace="3" alt="W" width=11 height=13></a></td> +<td align="center"><a rel="nofollow" href="$recentre"><img src="$host/i/rose.gif" alt="Recentre" width=35 height=34></a></td> +<td><a rel="nofollow" href="${compass[$x+1][$y]}"><img src="$host/i/arrow-east.gif" hspace="3" alt="E" width=11 height=13></a></td> +</tr> +<tr valign="top"> +<td align="right"><a rel="nofollow" href="${compass[$x-1][$y-1]}"><img src="$host/i/arrow-southwest.gif" alt="SW" width=11 height=11></a></td> +<td align="center"><a rel="nofollow" href="${compass[$x][$y-1]}"><img src="$host/i/arrow-south.gif" vspace="3" alt="S" width=13 height=11></a></td> +<td><a rel="nofollow" href="${compass[$x+1][$y-1]}"><img src="$host/i/arrow-southeast.gif" alt="SE" width=11 height=11></a></td> +</tr> +</table> +EOF +} + +1; diff --git a/perllib/FixMyStreet/Map/Bing.pm b/perllib/FixMyStreet/Map/Bing.pm new file mode 100644 index 000000000..8446a10fd --- /dev/null +++ b/perllib/FixMyStreet/Map/Bing.pm @@ -0,0 +1,71 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map::Bing +# Bing maps on FixMyStreet. +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map; + +use strict; +use mySociety::GeoUtil; +use mySociety::Web qw(ent); + +sub header_js { + return ' +<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&mkt=en-GB"></script> +<script type="text/javascript" src="/js/map-bing.js"></script> +'; +} + +# display_map Q PARAMS +# PARAMS include: +# EASTING, NORTHING 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 +# PRE/POST are HTML to show above/below map +sub display_map { + my ($q, %params) = @_; + $params{pre} ||= ''; + $params{post} ||= ''; + + foreach my $pin (@{$params{pins}}) { + } + + my $out = FixMyStreet::Map::header($q, $params{type}); + my ($lat, $lon) = mySociety::GeoUtil::national_grid_to_wgs84($params{easting}, $params{northing}, 'G'); + my $copyright = _('Map contains Ordnance Survey data © Crown copyright and database right 2010.'); + $out .= <<EOF; +<script type="text/javascript"> +var fixmystreet = { + 'lat': $lat, + 'lon': $lon +} +</script> +<div id="map_box"> + $params{pre} + <div id="map"></div> + <p id="copyright">$copyright</p> + $params{post} +</div> +<div id="side"> +EOF + return $out; +} + +sub display_map_end { + my ($type) = @_; + my $out = '</div>'; + $out .= '</form>' if ($type); + return $out; +} + +sub display_pin { +} + +sub map_pins { +} + +1; diff --git a/perllib/FixMyStreet/Map/BingOL.pm b/perllib/FixMyStreet/Map/BingOL.pm new file mode 100644 index 000000000..3939a710f --- /dev/null +++ b/perllib/FixMyStreet/Map/BingOL.pm @@ -0,0 +1,71 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map::Bing +# Bing maps on FixMyStreet. +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map; + +use strict; +use mySociety::Web qw(ent); + +sub header_js { + return ' +<!-- <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&mkt=en-GB"></script> --> +<script type="text/javascript" src="http://openlayers.org/api/OpenLayers.js"></script> +<script type="text/javascript" src="/js/map-bing-ol.js"></script> +<script type="text/javascript" src="/js/OpenLayers.Projection.OrdnanceSurvey.js"></script> +'; +} + +# display_map Q PARAMS +# PARAMS include: +# EASTING, NORTHING 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 +# PRE/POST are HTML to show above/below map +sub display_map { + my ($q, %params) = @_; + $params{pre} ||= ''; + $params{post} ||= ''; + + foreach my $pin (@{$params{pins}}) { + } + + my $out = FixMyStreet::Map::header($q, $params{type}); + my $copyright = _('Map contains Ordnance Survey data © Crown copyright and database right 2010. Microsoft'); + $out .= <<EOF; +<script type="text/javascript"> +var fixmystreet = { + 'easting': $params{easting}, + 'northing': $params{northing} +} +</script> +<div id="map_box"> + $params{pre} + <div id="map"></div> + <p id="copyright">$copyright</p> + $params{post} +</div> +<div id="side"> +EOF + return $out; +} + +sub display_map_end { + my ($type) = @_; + my $out = '</div>'; + $out .= '</form>' if ($type); + return $out; +} + +sub display_pin { +} + +sub map_pins { +} + +1; diff --git a/perllib/FixMyStreet/Map/Google.pm b/perllib/FixMyStreet/Map/Google.pm new file mode 100644 index 000000000..7a314efad --- /dev/null +++ b/perllib/FixMyStreet/Map/Google.pm @@ -0,0 +1,71 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map::Google +# Google maps on FixMyStreet. +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map; + +use strict; +use mySociety::GeoUtil; +use mySociety::Web qw(ent); + +sub header_js { + return ' +<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script> +<script type="text/javascript" src="/js/map-google.js"></script> +'; +} + +# display_map Q PARAMS +# PARAMS include: +# EASTING, NORTHING 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 +# PRE/POST are HTML to show above/below map +sub display_map { + my ($q, %params) = @_; + $params{pre} ||= ''; + $params{post} ||= ''; + + foreach my $pin (@{$params{pins}}) { + } + + my $out = FixMyStreet::Map::header($q, $params{type}); + my ($lat, $lon) = mySociety::GeoUtil::national_grid_to_wgs84($params{easting}, $params{northing}, 'G'); + my $copyright = _('Map contains Ordnance Survey data © Crown copyright and database right 2010.'); + $out .= <<EOF; +<script type="text/javascript"> +var fixmystreet = { + 'lat': $lat, + 'lon': $lon +} +</script> +<div id="map_box"> + $params{pre} + <div id="map"></div> + <p id="copyright">$copyright</p> + $params{post} +</div> +<div id="side"> +EOF + return $out; +} + +sub display_map_end { + my ($type) = @_; + my $out = '</div>'; + $out .= '</form>' if ($type); + return $out; +} + +sub display_pin { +} + +sub map_pins { +} + +1; diff --git a/perllib/FixMyStreet/Map/OSM.pm b/perllib/FixMyStreet/Map/OSM.pm new file mode 100644 index 000000000..ccbb3ca53 --- /dev/null +++ b/perllib/FixMyStreet/Map/OSM.pm @@ -0,0 +1,71 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map::OSM +# OSM maps on FixMyStreet. +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map; + +use strict; +use mySociety::Web qw(ent); + +sub header_js { + return ' +<script type="text/javascript" src="http://openlayers.org/api/OpenLayers.js"></script> +<script type="text/javascript" src="/js/map-OpenStreetMap.js"></script> +<script type="text/javascript" src="/js/OpenLayers.Projection.OrdnanceSurvey.js"></script> +'; +} + +# display_map Q PARAMS +# PARAMS include: +# EASTING, NORTHING 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 +# PRE/POST are HTML to show above/below map +sub display_map { + my ($q, %params) = @_; + $params{pre} ||= ''; + $params{post} ||= ''; + + foreach my $pin (@{$params{pins}}) { + } + + my $out = FixMyStreet::Map::header($q, $params{type}); + my $copyright = _('Map © <a href="http://www.openstreetmap.org/">OpenStreetMap</a> and contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'); + $out .= <<EOF; +<script type="text/javascript"> +var fixmystreet = { + 'easting': $params{easting}, + 'northing': $params{northing}, + 'map_type': OpenLayers.Layer.OSM.Mapnik +} +</script> +<div id="map_box"> + $params{pre} + <div id="map"></div> + <p id="copyright">$copyright</p> + $params{post} +</div> +<div id="side"> +EOF + return $out; +} + +sub display_map_end { + my ($type) = @_; + my $out = '</div>'; + $out .= '</form>' if ($type); + return $out; +} + +sub display_pin { +} + +sub map_pins { +} + +1; diff --git a/perllib/FixMyStreet/Map/OSM/CycleMap.pm b/perllib/FixMyStreet/Map/OSM/CycleMap.pm new file mode 100644 index 000000000..01c51acf4 --- /dev/null +++ b/perllib/FixMyStreet/Map/OSM/CycleMap.pm @@ -0,0 +1,71 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map::OSM::CycleMap +# OSM CycleMap maps on FixMyStreet. +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map; + +use strict; +use mySociety::Web qw(ent); + +sub header_js { + return ' +<script type="text/javascript" src="http://openlayers.org/api/OpenLayers.js"></script> +<script type="text/javascript" src="/js/map-OpenStreetMap.js"></script> +<script type="text/javascript" src="/js/OpenLayers.Projection.OrdnanceSurvey.js"></script> +'; +} + +# display_map Q PARAMS +# PARAMS include: +# EASTING, NORTHING 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 +# PRE/POST are HTML to show above/below map +sub display_map { + my ($q, %params) = @_; + $params{pre} ||= ''; + $params{post} ||= ''; + + foreach my $pin (@{$params{pins}}) { + } + + my $out = FixMyStreet::Map::header($q, $params{type}); + my $copyright = _('Map © <a href="http://www.openstreetmap.org/">OpenStreetMap</a> and contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'); + $out .= <<EOF; +<script type="text/javascript"> +var fixmystreet = { + 'easting': $params{easting}, + 'northing': $params{northing}, + 'map_type': OpenLayers.Layer.OSM.CycleMap +} +</script> +<div id="map_box"> + $params{pre} + <div id="map"></div> + <p id="copyright">$copyright</p> + $params{post} +</div> +<div id="side"> +EOF + return $out; +} + +sub display_map_end { + my ($type) = @_; + my $out = '</div>'; + $out .= '</form>' if ($type); + return $out; +} + +sub display_pin { +} + +sub map_pins { +} + +1; diff --git a/perllib/FixMyStreet/Map/OSM/StreetView.pm b/perllib/FixMyStreet/Map/OSM/StreetView.pm new file mode 100644 index 000000000..08f677d25 --- /dev/null +++ b/perllib/FixMyStreet/Map/OSM/StreetView.pm @@ -0,0 +1,70 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map::OSM::StreetView +# OSM StreetView maps on FixMyStreet. +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map; + +use strict; +use mySociety::Web qw(ent); + +sub header_js { + return ' +<script type="text/javascript" src="http://openlayers.org/api/OpenLayers.js"></script> +<script type="text/javascript" src="/js/map-streetview.js"></script> +<script type="text/javascript" src="/js/OpenLayers.Projection.OrdnanceSurvey.js"></script> +'; +} + +# display_map Q PARAMS +# PARAMS include: +# EASTING, NORTHING 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 +# PRE/POST are HTML to show above/below map +sub display_map { + my ($q, %params) = @_; + $params{pre} ||= ''; + $params{post} ||= ''; + + foreach my $pin (@{$params{pins}}) { + } + + my $out = FixMyStreet::Map::header($q, $params{type}); + my $copyright = _('Map contains Ordnance Survey data © Crown copyright and database right 2010.'); + $out .= <<EOF; +<script type="text/javascript"> +var fixmystreet = { + 'easting': $params{easting}, + 'northing': $params{northing} +} +</script> +<div id="map_box"> + $params{pre} + <div id="map"></div> + <p id="copyright">$copyright</p> + $params{post} +</div> +<div id="side"> +EOF + return $out; +} + +sub display_map_end { + my ($type) = @_; + my $out = '</div>'; + $out .= '</form>' if ($type); + return $out; +} + +sub display_pin { +} + +sub map_pins { +} + +1; diff --git a/perllib/FixMyStreet/Map/Tilma/OL/1_10k.pm b/perllib/FixMyStreet/Map/Tilma/OL/1_10k.pm new file mode 100644 index 000000000..b1fe0126d --- /dev/null +++ b/perllib/FixMyStreet/Map/Tilma/OL/1_10k.pm @@ -0,0 +1,83 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map::Tilma::1_10k_OL +# Using tilma.mysociety.org with OpenLayers +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map; + +use strict; + +use constant TILE_WIDTH => 254; +use constant TIF_SIZE_M => 5000; +use constant TIF_SIZE_PX => 7874; +use constant SCALE_FACTOR => TIF_SIZE_M / (TIF_SIZE_PX / TILE_WIDTH); +use constant TILE_TYPE => '10k-full'; + +sub header_js { + return ' +<script type="text/javascript" src="http://openlayers.org/api/OpenLayers.js"></script> +<script type="text/javascript" src="/js/map-tilma-ol.js"></script> +'; +} + +# display_map Q PARAMS +# PARAMS include: +# EASTING, NORTHING 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 +# PRE/POST are HTML to show above/below map +sub display_map { + my ($q, %params) = @_; + $params{pre} ||= ''; + $params{post} ||= ''; + + foreach my $pin (@{$params{pins}}) { + } + + my $out = FixMyStreet::Map::header($q, $params{type}); + my $tile_width = TILE_WIDTH; + my $tile_type = TILE_TYPE; + my $sf = SCALE_FACTOR / TILE_WIDTH; + my $copyright = _('© Crown copyright. All rights reserved. Ministry of Justice 100037819 2008.'); + $out .= <<EOF; +<script type="text/javascript"> +var fixmystreet = { + 'tilewidth': $tile_width, + 'tileheight': $tile_width, + 'easting': $params{easting}, + 'northing': $params{northing}, + 'tile_type': '$tile_type', + 'maxResolution': $sf +}; +</script> +<div id="map_box"> + $params{pre} + <div id="map"> + <div id="watermark"></div> + </div> + <p id="copyright">$copyright</p> +$params{post} +</div> +<div id="side"> +EOF + return $out; +} + +sub display_map_end { + my ($type) = @_; + my $out = '</div>'; + $out .= '</form>' if ($type); + return $out; +} + +sub display_pin { +} + +sub map_pins { +} + +1; diff --git a/perllib/FixMyStreet/Map/Tilma/OL/StreetView.pm b/perllib/FixMyStreet/Map/Tilma/OL/StreetView.pm new file mode 100644 index 000000000..7ef372351 --- /dev/null +++ b/perllib/FixMyStreet/Map/Tilma/OL/StreetView.pm @@ -0,0 +1,81 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map::TilmaXY +# Using tilma.mysociety.org but accessing images directly. +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map; + +use strict; + +use constant TILE_WIDTH => 250; +use constant TIF_SIZE_M => 5000; +use constant TIF_SIZE_PX => 5000; +use constant SCALE_FACTOR => TIF_SIZE_M / (TIF_SIZE_PX / TILE_WIDTH); +use constant TILE_TYPE => 'streetview'; + +sub header_js { + return ' +<script type="text/javascript" src="http://openlayers.org/api/OpenLayers.js"></script> +<script type="text/javascript" src="/js/map-tilma-ol.js"></script> +'; +} + +# display_map Q PARAMS +# PARAMS include: +# EASTING, NORTHING 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 +# PRE/POST are HTML to show above/below map +sub display_map { + my ($q, %params) = @_; + $params{pre} ||= ''; + $params{post} ||= ''; + + foreach my $pin (@{$params{pins}}) { + } + + my $out = FixMyStreet::Map::header($q, $params{type}); + my $tile_width = TILE_WIDTH; + my $tile_type = TILE_TYPE; + my $sf = SCALE_FACTOR / TILE_WIDTH; + my $copyright = _('Map contains Ordnance Survey data © Crown copyright and database right 2010.'); + $out .= <<EOF; +<script type="text/javascript"> +var fixmystreet = { + 'tilewidth': $tile_width, + 'tileheight': $tile_width, + 'easting': $params{easting}, + 'northing': $params{northing}, + 'tile_type': '$tile_type', + 'maxResolution': $sf +}; +</script> +<div id="map_box"> + $params{pre} + <div id="map"></div> + <p id="copyright">$copyright</p> + $params{post} +</div> +<div id="side"> +EOF + return $out; +} + +sub display_map_end { + my ($type) = @_; + my $out = '</div>'; + $out .= '</form>' if ($type); + return $out; +} + +sub display_pin { +} + +sub map_pins { +} + +1; diff --git a/perllib/FixMyStreet/Map/Tilma/Original.pm b/perllib/FixMyStreet/Map/Tilma/Original.pm new file mode 100644 index 000000000..5772f6ccd --- /dev/null +++ b/perllib/FixMyStreet/Map/Tilma/Original.pm @@ -0,0 +1,257 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map +# Adding the ability to have different maps on FixMyStreet. +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map; + +use strict; +use LWP::Simple; + +use Cobrand; +use mySociety::Web qw(ent NewURL); + +sub header_js { + return ' +<script type="text/javascript" src="/js/map-tilma.js"></script> +'; +} + +# display_map Q PARAMS +# PARAMS include: +# EASTING, NORTHING 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 +# PRE/POST are HTML to show above/below map +sub _display_map { + my ($q, %params) = @_; + $params{pre} ||= ''; + $params{post} ||= ''; + my $mid_point = TILE_WIDTH; # Map is 2 TILE_WIDTHs in size, square. + if ($q->{site} eq 'barnet') { # Map is c. 380px wide + $mid_point = 189; + } + + # X/Y tile co-ords may be overridden in the query string + my @vars = qw(x y); + my %input = map { $_ => $q->param($_) || '' } @vars; + ($input{x}) = $input{x} =~ /^(\d+)/; $input{x} ||= 0; + ($input{y}) = $input{y} =~ /^(\d+)/; $input{y} ||= 0; + + my ($x, $y, $px, $py) = FixMyStreet::Map::os_to_px_with_adjust($q, $params{easting}, $params{northing}, $input{x}, $input{y}); + + my $pins = ''; + foreach my $pin (@{$params{pins}}) { + my $pin_x = FixMyStreet::Map::os_to_px($pin->[0], $x); + my $pin_y = FixMyStreet::Map::os_to_px($pin->[1], $y, 1); + $pins .= FixMyStreet::Map::display_pin($q, $pin_x, $pin_y, $pin->[2]); + } + + $px = defined($px) ? $mid_point - $px : 0; + $py = defined($py) ? $mid_point - $py : 0; + $x = int($x)<=0 ? 0 : $x; + $y = int($y)<=0 ? 0 : $y; + my $url = 'http://tilma.mysociety.org/tileserver/' . TILE_TYPE . '/'; + my $tiles_url = $url . ($x-1) . '-' . $x . ',' . ($y-1) . '-' . $y . '/RABX'; + my $tiles = LWP::Simple::get($tiles_url); + return '<div id="map_box"> <div id="map"><div id="drag">' . _("Unable to fetch the map tiles from the tile server.") . '</div></div></div><div id="side">' if !$tiles; + my $tileids = RABX::unserialise($tiles); + my $tl = ($x-1) . '.' . $y; + my $tr = $x . '.' . $y; + my $bl = ($x-1) . '.' . ($y-1); + my $br = $x . '.' . ($y-1); + return '<div id="side">' if (!$tileids->[0][0] || !$tileids->[0][1] || !$tileids->[1][0] || !$tileids->[1][1]); + my $tl_src = $url . $tileids->[0][0]; + my $tr_src = $url . $tileids->[0][1]; + my $bl_src = $url . $tileids->[1][0]; + my $br_src = $url . $tileids->[1][1]; + + my $cobrand = Page::get_cobrand($q); + my $root_path_js = Cobrand::root_path_js($cobrand, $q); + my $out = FixMyStreet::Map::header($q, $params{type}); + my $img_type; + if ($params{type}) { + $out .= <<EOF; +<input type="hidden" name="x" id="formX" value="$x"> +<input type="hidden" name="y" id="formY" value="$y"> +EOF + $img_type = '<input type="image"'; + } else { + $img_type = '<img'; + } + my $imgw = TILE_WIDTH . 'px'; + my $tile_width = TILE_WIDTH; + my $tile_type = TILE_TYPE; + $out .= <<EOF; +<script type="text/javascript"> +$root_path_js +var fixmystreet = { + 'x': $x - 3, + 'y': $y - 3, + 'start_x': $px, + 'start_y': $py, + 'tile_type': '$tile_type', + 'tilewidth': $tile_width, + 'tileheight': $tile_width +}; +</script> +<div id="map_box"> +$params{pre} + <div id="map"><div id="drag"> + $img_type alt="NW map tile" id="t2.2" name="tile_$tl" src="$tl_src" style="top:0px; left:0;">$img_type alt="NE map tile" id="t2.3" name="tile_$tr" src="$tr_src" style="top:0px; left:$imgw;"><br>$img_type alt="SW map tile" id="t3.2" name="tile_$bl" src="$bl_src" style="top:$imgw; left:0;">$img_type alt="SE map tile" id="t3.3" name="tile_$br" src="$br_src" style="top:$imgw; left:$imgw;"> + <div id="pins">$pins</div> + </div> +EOF + $out .= '<div id="watermark"></div>' if $params{watermark}; + $out .= compass($q, $x, $y); + my $copyright = $params{copyright}; + $out .= <<EOF; + </div> + <p id="copyright">$copyright</p> +$params{post} +</div> +<div id="side"> +EOF + return $out; +} + +sub display_map_end { + my ($type) = @_; + my $out = '</div>'; + $out .= '</form>' if ($type); + return $out; +} + +sub display_pin { + my ($q, $px, $py, $col, $num) = @_; + $num = '' if !$num || $num > 9; + my $host = Page::base_url_with_lang($q, undef); + my %cols = (red=>'R', green=>'G', blue=>'B', purple=>'P'); + my $out = '<img class="pin" src="' . $host . '/i/pin' . $cols{$col} + . $num . '.gif" alt="' . _('Problem') . '" style="top:' . ($py-59) + . 'px; left:' . ($px) . 'px; position: absolute;">'; + return $out unless $_ && $_->{id} && $col ne 'blue'; + my $cobrand = Page::get_cobrand($q); + my $url = Cobrand::url($cobrand, NewURL($q, -retain => 1, + -url => '/report/' . $_->{id}, + pc => undef, + x => undef, + y => undef, + sx => undef, + sy => undef, + all_pins => undef, + no_pins => undef), $q); + $out = '<a title="' . ent($_->{title}) . '" href="' . $url . '">' . $out . '</a>'; + return $out; +} + +sub map_pins { + my ($q, $x, $y, $sx, $sy, $interval) = @_; + + 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 $pins = ''; + foreach (@$around_map) { + 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'; + $pins .= FixMyStreet::Map::display_pin($q, $px, $py, $col); + } + + foreach (@$nearby) { + 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'; + $pins .= FixMyStreet::Map::display_pin($q, $px, $py, $col); + } + + return ($pins, $around_map_list, $nearby, $dist); +} + +# P is easting or northing +# C is centre tile reference of displayed map +sub os_to_px { + my ($p, $c, $invert) = @_; + return tile_to_px(os_to_tile($p), $c, $invert); +} + +# Convert tile co-ordinates to pixel co-ordinates from top left of map +# C is centre tile reference of displayed map +sub tile_to_px { + my ($p, $c, $invert) = @_; + $p = TILE_WIDTH * ($p - $c + 1); + $p = 2 * TILE_WIDTH - $p if $invert; + $p = int($p + .5 * ($p <=> 0)); + return $p; +} + +# Tile co-ordinates are linear scale of OS E/N +# Will need more generalising when more zooms appear +sub os_to_tile { + return $_[0] / SCALE_FACTOR; +} +sub tile_to_os { + return int($_[0] * SCALE_FACTOR + 0.5); +} + +sub click_to_tile { + my ($pin_tile, $pin, $invert) = @_; + $pin -= TILE_WIDTH while $pin > TILE_WIDTH; + $pin += TILE_WIDTH while $pin < 0; + $pin = TILE_WIDTH - $pin if $invert; # image submits measured from top down + return $pin_tile + $pin / TILE_WIDTH; +} + +# Given some click co-ords (the tile they were on, and where in the +# tile they were), convert to OSGB36 and return. +sub click_to_os { + my ($pin_tile_x, $pin_x, $pin_tile_y, $pin_y) = @_; + my $tile_x = FixMyStreet::Map::click_to_tile($pin_tile_x, $pin_x); + my $tile_y = FixMyStreet::Map::click_to_tile($pin_tile_y, $pin_y, 1); + my $easting = FixMyStreet::Map::tile_to_os($tile_x); + my $northing = FixMyStreet::Map::tile_to_os($tile_y); + return ($easting, $northing); +} + +# 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. +sub os_to_px_with_adjust { + my ($q, $easting, $northing, $in_x, $in_y) = @_; + + my $x = FixMyStreet::Map::os_to_tile($easting); + my $y = FixMyStreet::Map::os_to_tile($northing); + my $x_tile = $in_x || int($x); + my $y_tile = $in_y || int($y); + + # Try and have point near centre of map + if (!$in_x && $x - $x_tile > 0.5) { + $x_tile += 1; + } + if (!$in_y && $y - $y_tile > 0.5) { + $y_tile += 1; + } + + my $px = FixMyStreet::Map::os_to_px($easting, $x_tile); + my $py = FixMyStreet::Map::os_to_px($northing, $y_tile, 1); + if ($q->{site} eq 'barnet') { # Map is 380px, so might need to adjust + if (!$in_x && $px > 380) { + $x_tile++; + $px = FixMyStreet::Map::os_to_px($easting, $x_tile); + } + if (!$in_y && $py > 380) { + $y_tile--; + $py = FixMyStreet::Map::os_to_px($northing, $y_tile, 1); + } + } + + return ($x_tile, $y_tile, $px, $py); +} + +1; diff --git a/perllib/FixMyStreet/Map/Tilma/Original/1_10k.pm b/perllib/FixMyStreet/Map/Tilma/Original/1_10k.pm new file mode 100644 index 000000000..f97163c68 --- /dev/null +++ b/perllib/FixMyStreet/Map/Tilma/Original/1_10k.pm @@ -0,0 +1,28 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map +# Adding the ability to have different maps on FixMyStreet. +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map; + +use strict; + +use constant TILE_WIDTH => 254; +use constant TIF_SIZE_M => 5000; +use constant TIF_SIZE_PX => 7874; +use constant SCALE_FACTOR => TIF_SIZE_M / (TIF_SIZE_PX / TILE_WIDTH); +use constant TILE_TYPE => '10k-full'; + +use FixMyStreet::Map::Tilma::Original; + +sub display_map { + my ($q, %params) = @_; + $params{copyright} = _('© Crown copyright. All rights reserved. Ministry of Justice 100037819 2008.'); + $params{watermark} = 1; + return _display_map($q, %params); +} + +1; diff --git a/perllib/FixMyStreet/Map/Tilma/Original/StreetView.pm b/perllib/FixMyStreet/Map/Tilma/Original/StreetView.pm new file mode 100644 index 000000000..103f4c15c --- /dev/null +++ b/perllib/FixMyStreet/Map/Tilma/Original/StreetView.pm @@ -0,0 +1,27 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map +# Adding the ability to have different maps on FixMyStreet. +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map; + +use strict; + +use constant TILE_WIDTH => 250; +use constant TIF_SIZE_M => 5000; +use constant TIF_SIZE_PX => 5000; +use constant SCALE_FACTOR => TIF_SIZE_M / (TIF_SIZE_PX / TILE_WIDTH); +use constant TILE_TYPE => 'streetview'; + +use FixMyStreet::Map::Tilma::Original; + +sub display_map { + my ($q, %params) = @_; + $params{copyright} = _('Map contains Ordnance Survey data © Crown copyright and database right 2010.'); + return _display_map($q, %params); +} + +1; diff --git a/perllib/Page.pm b/perllib/Page.pm index 127fad11b..1b94f5c2b 100644 --- a/perllib/Page.pm +++ b/perllib/Page.pm @@ -16,41 +16,34 @@ 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); +# Under the BEGIN so that the config has been set. +use FixMyStreet::Map; my $lastmodified; @@ -205,12 +198,17 @@ sub template_vars ($%) { 'lang_url' => $lang_url, 'title' => $params{title}, 'rss' => '', + map_js => FixMyStreet::Map::header_js(), ); if ($params{rss}) { $vars{rss} = '<link rel="alternate" type="application/rss+xml" title="' . $params{rss}[0] . '" href="' . $params{rss}[1] . '">'; } + if ($params{robots}) { + $vars{robots} = '<meta name="robots" content="' . $params{robots} . '">'; + } + if ($q->{site} eq 'fixmystreet') { my $home = !$params{title} && $ENV{SCRIPT_NAME} eq '/index.cgi' && !$ENV{QUERY_STRING}; $vars{heading_element_start} = $home ? '<h1 id="header">' : '<div id="header"><a href="/">'; @@ -285,7 +283,7 @@ sub header ($%) { my $default_params = Cobrand::header_params(get_cobrand($q), $q, %params); my %default_params = %{$default_params}; %params = (%default_params, %params); - my %permitted_params = map { $_ => 1 } qw(title rss js expires lastmodified template cachecontrol context status_code); + my %permitted_params = map { $_ => 1 } qw(title rss expires lastmodified template cachecontrol context status_code robots); foreach (keys %params) { croak "bad parameter '$_'" if (!exists($permitted_params{$_})); } @@ -318,8 +316,6 @@ sub header ($%) { sub footer { my ($q, %params) = @_; my $extra = $params{extra}; - my $js = $params{js} || ''; - $js = ''; # Don't use fileupload JS at the moment if ($q->{site} ne 'fixmystreet') { my $template = template($q, %params) . '-footer'; @@ -381,8 +377,6 @@ $orglogo $track -$js - $piwik </body> @@ -401,275 +395,6 @@ sub error_page ($$) { print $q->header(-content_length => length($html)), $html; } -# display_map Q PARAMS -# PARAMS include: -# X,Y is bottom left tile of 2x2 grid -# TYPE is 1 if the map is clickable, 2 if clickable and has a form upload, -# 0 if not clickable -# PINS is HTML of pins to show -# PX,PY are coordinates of pin -# PRE/POST are HTML to show above/below map -sub display_map { - my ($q, %params) = @_; - $params{pins} ||= ''; - $params{pre} ||= ''; - $params{post} ||= ''; - my $mid_point = TILE_WIDTH; # Map is 2 TILE_WIDTHs in size, square. - if ($q->{site} eq 'barnet') { # Map is c. 380px wide - $mid_point = 189; - } - my $px = defined($params{px}) ? $mid_point - $params{px} : 0; - my $py = defined($params{py}) ? $mid_point - $params{py} : 0; - my $x = int($params{x})<=0 ? 0 : $params{x}; - my $y = int($params{y})<=0 ? 0 : $params{y}; - my $url = mySociety::Config::get('TILES_URL'); - my $tiles_url = $url . ($x-1) . '-' . $x . ',' . ($y-1) . '-' . $y . '/RABX'; - my $tiles = LWP::Simple::get($tiles_url); - return '<div id="map_box"> <div id="map"><div id="drag">' . _("Unable to fetch the map tiles from the tile server.") . '</div></div></div><div id="side">' if !$tiles; - my $tileids = RABX::unserialise($tiles); - my $tl = ($x-1) . '.' . $y; - my $tr = $x . '.' . $y; - my $bl = ($x-1) . '.' . ($y-1); - my $br = $x . '.' . ($y-1); - return '<div id="side">' if (!$tileids->[0][0] || !$tileids->[0][1] || !$tileids->[1][0] || !$tileids->[1][1]); - my $tl_src = $url . $tileids->[0][0]; - my $tr_src = $url . $tileids->[0][1]; - my $bl_src = $url . $tileids->[1][0]; - my $br_src = $url . $tileids->[1][1]; - - my $out = ''; - my $cobrand = Page::get_cobrand($q); - my $root_path_js = Cobrand::root_path_js($cobrand, $q); - my $cobrand_form_elements = Cobrand::form_elements($cobrand, 'mapForm', $q); - my $img_type; - my $form_action = Cobrand::url($cobrand, '', $q); - if ($params{type}) { - my $encoding = ''; - $encoding = ' enctype="multipart/form-data"' if ($params{type}==2); - my $pc = $q->param('pc') || ''; - my $pc_enc = ent($pc); - $out .= <<EOF; -<form action="$form_action" method="post" name="mapForm" id="mapForm"$encoding> -<input type="hidden" name="submit_map" value="1"> -<input type="hidden" name="x" id="formX" value="$x"> -<input type="hidden" name="y" id="formY" value="$y"> -<input type="hidden" name="pc" value="$pc_enc"> -$cobrand_form_elements -EOF - $img_type = '<input type="image"'; - } else { - $img_type = '<img'; - } - my $imgw = TILE_WIDTH . 'px'; - my $tile_width = TILE_WIDTH; - my $tile_type = mySociety::Config::get('TILES_TYPE'); - $out .= <<EOF; -<script type="text/javascript"> -$root_path_js -var fixmystreet = { - 'x': $x - 3, - 'y': $y - 3, - 'start_x': $px, - 'start_y': $py, - 'tile_type': '$tile_type', - 'tilewidth': $tile_width, - 'tileheight': $tile_width -}; -</script> -<div id="map_box"> -$params{pre} - <div id="map"><div id="drag"> - $img_type alt="NW map tile" id="t2.2" name="tile_$tl" src="$tl_src" style="top:0px; left:0;">$img_type alt="NE map tile" id="t2.3" name="tile_$tr" src="$tr_src" style="top:0px; left:$imgw;"><br>$img_type alt="SW map tile" id="t3.2" name="tile_$bl" src="$bl_src" style="top:$imgw; left:0;">$img_type alt="SE map tile" id="t3.3" name="tile_$br" src="$br_src" style="top:$imgw; left:$imgw;"> - <div id="pins">$params{pins}</div> - </div> -EOF - if (Cobrand::show_watermark($cobrand) && mySociety::Config::get('TILES_TYPE') ne 'streetview') { - $out .= '<div id="watermark"></div>'; - } - $out .= compass($q, $x, $y); - my $copyright; - if (mySociety::Config::get('TILES_TYPE') eq 'streetview') { - $copyright = _('Map contains Ordnance Survey data © Crown copyright and database right 2010.'); - } else { - $copyright = _('© Crown copyright. All rights reserved. Ministry of Justice 100037819 2008.'); - } - $out .= <<EOF; - </div> - <p id="copyright">$copyright</p> -$params{post} -EOF - $out .= '</div>'; - $out .= '<div id="side">'; - return $out; -} - -sub display_map_end { - my ($type) = @_; - my $out = '</div>'; - $out .= '</form>' if ($type); - return $out; -} - -sub display_pin { - my ($q, $px, $py, $col, $num) = @_; - $num = '' if !$num || $num > 9; - my $host = base_url_with_lang($q, undef); - my %cols = (red=>'R', green=>'G', blue=>'B', purple=>'P'); - my $out = '<img class="pin" src="' . $host . '/i/pin' . $cols{$col} - . $num . '.gif" alt="' . _('Problem') . '" style="top:' . ($py-59) - . 'px; left:' . ($px) . 'px; position: absolute;">'; - return $out unless $_ && $_->{id} && $col ne 'blue'; - my $cobrand = Page::get_cobrand($q); - my $url = Cobrand::url($cobrand, NewURL($q, -retain => 1, - -url => '/report/' . $_->{id}, - pc => undef, - x => undef, - y => undef, - sx => undef, - sy => undef, - all_pins => undef, - no_pins => undef), $q); - $out = '<a title="' . ent($_->{title}) . '" href="' . $url . '">' . $out . '</a>'; - return $out; -} - -sub map_pins { - my ($q, $x, $y, $sx, $sy, $interval) = @_; - - my $pins = ''; - my $min_e = Page::tile_to_os($x-3); # Extra space to left/below due to rounding, I think - my $min_n = Page::tile_to_os($y-3); - my $mid_e = Page::tile_to_os($x); - my $mid_n = Page::tile_to_os($y); - my $max_e = Page::tile_to_os($x+2); - my $max_n = Page::tile_to_os($y+2); - my $cobrand = Page::get_cobrand($q); - # list of problems aoround map can be limited, but should show all pins - my $around_limit = Cobrand::on_map_list_limit($cobrand); - my $around_map; - my $around_map_list = Problems::around_map($min_e, $max_e, $min_n, $max_n, $interval, $around_limit); - if ($around_limit) { - $around_map = Problems::around_map($min_e, $max_e, $min_n, $max_n, $interval, undef); - } else { - $around_map = $around_map_list; - } - my @ids = (); - foreach (@$around_map_list) { - push(@ids, $_->{id}); - } - foreach (@$around_map) { - my $px = Page::os_to_px($_->{easting}, $sx); - my $py = Page::os_to_px($_->{northing}, $sy, 1); - my $col = $_->{state} eq 'fixed' ? 'green' : 'red'; - $pins .= Page::display_pin($q, $px, $py, $col); - } - - 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 = int($dist*10+0.5)/10; - - my $limit = 20; # - @$current_map; - my $nearby = Problems::nearby($dist, join(',', @ids), $limit, $mid_e, $mid_n, $interval); - foreach (@$nearby) { - my $px = Page::os_to_px($_->{easting}, $sx); - my $py = Page::os_to_px($_->{northing}, $sy, 1); - my $col = $_->{state} eq 'fixed' ? 'green' : 'red'; - $pins .= Page::display_pin($q, $px, $py, $col); - } - - return ($pins, $around_map_list, $nearby, $dist); -} - -sub compass ($$$) { - 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); - } - } - my $recentre = NewURL($q); - my $host = base_url_with_lang($q, undef); - return <<EOF; -<table cellpadding="0" cellspacing="0" border="0" id="compass"> -<tr valign="bottom"> -<td align="right"><a rel="nofollow" href="${compass[$x-1][$y+1]}"><img src="$host/i/arrow-northwest.gif" alt="NW" width=11 height=11></a></td> -<td align="center"><a rel="nofollow" href="${compass[$x][$y+1]}"><img src="$host/i/arrow-north.gif" vspace="3" alt="N" width=13 height=11></a></td> -<td><a rel="nofollow" href="${compass[$x+1][$y+1]}"><img src="$host/i/arrow-northeast.gif" alt="NE" width=11 height=11></a></td> -</tr> -<tr> -<td><a rel="nofollow" href="${compass[$x-1][$y]}"><img src="$host/i/arrow-west.gif" hspace="3" alt="W" width=11 height=13></a></td> -<td align="center"><a rel="nofollow" href="$recentre"><img src="$host/i/rose.gif" alt="Recentre" width=35 height=34></a></td> -<td><a rel="nofollow" href="${compass[$x+1][$y]}"><img src="$host/i/arrow-east.gif" hspace="3" alt="E" width=11 height=13></a></td> -</tr> -<tr valign="top"> -<td align="right"><a rel="nofollow" href="${compass[$x-1][$y-1]}"><img src="$host/i/arrow-southwest.gif" alt="SW" width=11 height=11></a></td> -<td align="center"><a rel="nofollow" href="${compass[$x][$y-1]}"><img src="$host/i/arrow-south.gif" vspace="3" alt="S" width=13 height=11></a></td> -<td><a rel="nofollow" href="${compass[$x+1][$y-1]}"><img src="$host/i/arrow-southeast.gif" alt="SE" width=11 height=11></a></td> -</tr> -</table> -EOF -} - -# P is easting or northing -# C is centre tile reference of displayed map -sub os_to_px { - my ($p, $c, $invert) = @_; - return tile_to_px(os_to_tile($p), $c, $invert); -} - -# Convert tile co-ordinates to pixel co-ordinates from top left of map -# C is centre tile reference of displayed map -sub tile_to_px { - my ($p, $c, $invert) = @_; - $p = TILE_WIDTH * ($p - $c + 1); - $p = 2 * TILE_WIDTH - $p if $invert; - $p = int($p + .5 * ($p <=> 0)); - return $p; -} - -# Tile co-ordinates are linear scale of OS E/N -# Will need more generalising when more zooms appear -sub os_to_tile { - return $_[0] / SCALE_FACTOR; -} -sub tile_to_os { - return int($_[0] * SCALE_FACTOR + 0.5); -} - -sub click_to_tile { - my ($pin_tile, $pin, $invert) = @_; - $pin -= TILE_WIDTH while $pin > TILE_WIDTH; - $pin += TILE_WIDTH while $pin < 0; - $pin = TILE_WIDTH - $pin if $invert; # image submits measured from top down - return $pin_tile + $pin / TILE_WIDTH; -} - -sub os_to_px_with_adjust { - my ($q, $easting, $northing, $in_x, $in_y) = @_; - - my $x = Page::os_to_tile($easting); - my $y = Page::os_to_tile($northing); - my $x_tile = $in_x || int($x); - my $y_tile = $in_y || int($y); - my $px = Page::os_to_px($easting, $x_tile); - my $py = Page::os_to_px($northing, $y_tile, 1); - if ($q->{site} eq 'barnet') { # Map is 380px - if ($py > 380) { - $y_tile--; - $py = Page::os_to_px($northing, $y_tile, 1); - } - if ($px > 380) { - $x_tile++; - $px = Page::os_to_px($easting, $x_tile); - } - } - return ($x, $y, $x_tile, $y_tile, $px, $py); -} - # send_email TO (NAME) TEMPLATE-NAME PARAMETERS # TEMPLATE-NAME is currently one of problem, update, alert, tms sub send_email { @@ -927,143 +652,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 = Page::os_to_tile($easting); - my $yy = Page::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 = Page::os_to_tile($easting); - my $yy = Page::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/perllib/Standard.pm b/perllib/Standard.pm index eda2d89fb..571065c14 100644 --- a/perllib/Standard.pm +++ b/perllib/Standard.pm @@ -20,7 +20,6 @@ use lib "$FindBin::Bin/../commonlib/perllib"; use Page; - package Standard; sub import { diff --git a/t/Cobrand.t b/t/Cobrand.t index 1f3321b0d..f6c2e9c87 100755 --- a/t/Cobrand.t +++ b/t/Cobrand.t @@ -11,7 +11,7 @@ use strict; use warnings; -use Test::More tests => 62; +use Test::More tests => 59; use Test::Exception; use Error qw(:try); @@ -213,17 +213,6 @@ sub test_url { is($url, '/xyz', 'url returns passed url if there is no url function defined by the cobrand'); } -sub test_show_watermark { - my $cobrand = 'mysite'; - my $watermark = Cobrand::show_watermark($cobrand); - is($watermark, 0, 'show_watermark returns output from cobrand module'); - - $cobrand = 'nosite'; - $watermark = Cobrand::show_watermark($cobrand); - is($watermark, 1, 'watermark returns 1 if there is no show_watermark function defined by the cobrand'); - -} - sub test_allow_photo_upload { my $cobrand = 'mysite'; my $photo_upload = Cobrand::allow_photo_upload($cobrand); @@ -287,7 +276,6 @@ ok(test_root_path_js() == 1, 'Ran all tests for root_js'); ok(test_site_title() == 1, 'Ran all tests for site_title'); ok(test_on_map_list_limit() == 1, 'Ran all tests for on_map_list_limit'); ok(test_url() == 1, 'Ran all tests for url'); -ok(test_show_watermark() == 1, 'Ran all tests for show_watermark'); ok(test_allow_photo_upload() == 1, 'Ran all tests for allow_photo_upload'); ok(test_allow_photo_display() == 1, 'Ran all tests for allow_photo_display'); ok(test_council_check() == 1, 'Ran all tests for council_check'); diff --git a/t/Cobrands/Mysite/Util.pm b/t/Cobrands/Mysite/Util.pm index 8aedd2c8c..c1a8023c1 100644 --- a/t/Cobrands/Mysite/Util.pm +++ b/t/Cobrands/Mysite/Util.pm @@ -87,10 +87,6 @@ sub url { return '/transformed_url'; } -sub show_watermark { - return 0; -} - sub allow_photo_upload { return 0; } @@ -11,7 +11,7 @@ use strict; use warnings; -use Test::More tests => 15; +use Test::More tests => 13; use Test::Exception; use FindBin; @@ -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,12 @@ 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); - 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"); + 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 - ($x, $y, $easting, $northing, $error) = Page::geocode_string('Falls Road, Belfast', $q); + ($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/templates/website/cobrands/barnet/barnet-header b/templates/website/cobrands/barnet/barnet-header index 3474091e6..67619b417 100644 --- a/templates/website/cobrands/barnet/barnet-header +++ b/templates/website/cobrands/barnet/barnet-header @@ -51,6 +51,7 @@ <script type="text/javascript" src="/cobrands/barnet/javascript/2008-portsurf.js"></script>
<script type="text/javascript" src="/yui/utilities.js"></script>
<script type="text/javascript" src="/js.js"></script>
+{{ $map_js }}
{{ $rss }}
diff --git a/templates/website/cobrands/emptyhomes/emptyhomes-header b/templates/website/cobrands/emptyhomes/emptyhomes-header index 09a96b581..dfdefa8cc 100644 --- a/templates/website/cobrands/emptyhomes/emptyhomes-header +++ b/templates/website/cobrands/emptyhomes/emptyhomes-header @@ -5,6 +5,7 @@ <meta http-equiv="content-type" content="text/html; charset=utf-8"> <script type="text/javascript" src="/yui/utilities.js"></script> <script type="text/javascript" src="/js.js"></script> +{{ $map_js }} <style type="text/css"> @import "/css/core.css"; @import "/css/cobrands/emptyhomes/emptyhomes.css"; diff --git a/templates/website/header b/templates/website/header index 44f51ca55..028d05506 100644 --- a/templates/website/header +++ b/templates/website/header @@ -4,6 +4,9 @@ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script type="text/javascript" src="/yui/utilities.js"></script> <script type="text/javascript" src="/js.js"></script> + {{ $map_js }} + {{ $robots }} + <title>{{ $title }}{{ $site_title }}</title> <style type="text/css">@import url("/css/core.css"); @import url("/css/main.css");</style> <!--[if LT IE 7]> diff --git a/web/ajax.cgi b/web/ajax.cgi index fff437846..fa722f02e 100755 --- a/web/ajax.cgi +++ b/web/ajax.cgi @@ -35,7 +35,7 @@ sub main { unless ($input{all_pins}) { $interval = '6 months'; } - my ($pins, $on_map, $around_map, $dist) = Page::map_pins($q, $x, $y, $sx, $sy, $interval); + my ($pins, $on_map, $around_map, $dist) = FixMyStreet::Map::map_pins($q, $x, $y, $sx, $sy, $interval); my $cobrand = Page::get_cobrand($q); my $list = ''; my $link = ''; diff --git a/web/alert.cgi b/web/alert.cgi index 108a02683..71249759e 100755 --- a/web/alert.cgi +++ b/web/alert.cgi @@ -13,7 +13,8 @@ use Standard; use Digest::SHA1 qw(sha1_hex); use Error qw(:try); use CrossSell; -use mySociety::Alert; +use FixMyStreet::Alert; +use FixMyStreet::Geocode; use mySociety::AuthToken; use mySociety::Config; use mySociety::DBHandle qw(select_all); @@ -55,7 +56,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('x') && $q->param('y'))) { + } elsif ($q->param('pc') || ($q->param('e') && $q->param('n'))) { $title = _('Local RSS feeds and email alerts'); $out = alert_list($q); } else { @@ -63,7 +64,7 @@ EOF $out = alert_front_page($q); } - print Page::header($q, title => $title); + print Page::header($q, title => $title, robots => 'noindex,nofollow'); print $out; print Page::footer($q); } @@ -71,25 +72,22 @@ Page::do_fastcgi(\&main); sub alert_list { my ($q, @errors) = @_; - my @vars = qw(pc rznvy x y); + my @vars = qw(pc rznvy e n); my %input = map { $_ => scalar $q->param($_) } @vars; my %input_h = map { $_ => $q->param($_) ? ent($q->param($_)) : '' } @vars; my($error, $e, $n); - my $x = $input{x}; my $y = $input{y}; - $x ||= 0; $x += 0; - $y ||= 0; $y += 0; - if ($x || $y) { - $e = Page::tile_to_os($input{x}); - $n = Page::tile_to_os($input{y}); + if ($input{e} || $input{n}) { + $e = $input{e}; + $n = $input{n}; } else { try { - ($x, $y, $e, $n, $error) = Page::geocode($input{pc}, $q); + ($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}; @@ -273,8 +271,8 @@ EOF rss_feed_5k => $rss_feed_5k, rss_feed_10k => $rss_feed_10k, rss_feed_20k => $rss_feed_20k, - x => $x, - y => $y, + e => $e, + n => $n, options => $options ); my $cobrand_page = Page::template_include('alert-options', $q, Page::template_root($q), %vars); $out = $cobrand_page if ($cobrand_page); @@ -446,8 +444,8 @@ sub alert_signed_input { my $out; my $cobrand = Page::get_cobrand($q); if ($signed_email eq sha1_hex("$id-$email-$salt-$secret")) { - my $alert_id = mySociety::Alert::create($email, 'new_updates', $cobrand, '', $id); - mySociety::Alert::confirm($alert_id); + my $alert_id = FixMyStreet::Alert::create($email, 'new_updates', $cobrand, '', $id); + FixMyStreet::Alert::confirm($alert_id); $out = $q->p(_('You have successfully subscribed to that alert.')); my $cobrand = Page::get_cobrand($q); my $display_advert = Cobrand::allow_crosssell_adverts($cobrand); @@ -476,14 +474,14 @@ sub alert_token { my $message; my $display_advert = Cobrand::allow_crosssell_adverts($cobrand); if ($type eq 'subscribe') { - mySociety::Alert::confirm($id); + FixMyStreet::Alert::confirm($id); $message = _('You have successfully confirmed your alert.'); $out = $q->p($message); if ($display_advert) { $out .= CrossSell::display_advert($q, $email); } } elsif ($type eq 'unsubscribe') { - mySociety::Alert::delete($id); + FixMyStreet::Alert::delete($id); $message = _('You have successfully deleted your alert.'); $out = $q->p($message); if ($display_advert) { @@ -517,22 +515,22 @@ sub alert_do_subscribe { my $cobrand_data = Cobrand::extra_alert_data($cobrand, $q); if ($type eq 'updates') { my $id = $q->param('id'); - $alert_id = mySociety::Alert::create($email, 'new_updates', $cobrand, $cobrand_data, $id); + $alert_id = FixMyStreet::Alert::create($email, 'new_updates', $cobrand, $cobrand_data, $id); } elsif ($type eq 'problems') { - $alert_id = mySociety::Alert::create($email, 'new_problems', $cobrand, $cobrand_data); + $alert_id = FixMyStreet::Alert::create($email, 'new_problems', $cobrand, $cobrand_data); } elsif ($type eq 'local') { my $feed = $q->param('feed'); if ($feed =~ /^area:(?:\d+:)?(\d+)/) { - $alert_id = mySociety::Alert::create($email, 'area_problems', $cobrand, $cobrand_data, $1); + $alert_id = FixMyStreet::Alert::create($email, 'area_problems', $cobrand, $cobrand_data, $1); } elsif ($feed =~ /^council:(\d+)/) { - $alert_id = mySociety::Alert::create($email, 'council_problems', $cobrand, $cobrand_data, $1, $1); + $alert_id = FixMyStreet::Alert::create($email, 'council_problems', $cobrand, $cobrand_data, $1, $1); } elsif ($feed =~ /^ward:(\d+):(\d+)/) { - $alert_id = mySociety::Alert::create($email, 'ward_problems', $cobrand, $cobrand_data, $1, $2); + $alert_id = FixMyStreet::Alert::create($email, 'ward_problems', $cobrand, $cobrand_data, $1, $2); } elsif ($feed =~ /^local:(\d+):(\d+)/) { - $alert_id = mySociety::Alert::create($email, 'local_problems', $cobrand, $cobrand_data, $1, $2); + $alert_id = FixMyStreet::Alert::create($email, 'local_problems', $cobrand, $cobrand_data, $1, $2); } } else { - throw mySociety::Alert::Error('Invalid type'); + throw FixMyStreet::Alert::Error('Invalid type'); } my %h = (); diff --git a/web/confirm.cgi b/web/confirm.cgi index cd874e9f0..daa260d65 100755 --- a/web/confirm.cgi +++ b/web/confirm.cgi @@ -12,7 +12,7 @@ use strict; use Standard; use Digest::SHA1 qw(sha1_hex); use CrossSell; -use mySociety::Alert; +use FixMyStreet::Alert; use mySociety::AuthToken; use mySociety::Random qw(random_bytes); @@ -115,8 +115,8 @@ sub confirm_update { # Subscribe updater to email updates if requested if ($add_alert) { - my $alert_id = mySociety::Alert::create($email, 'new_updates', $cobrand, $cobrand_data, $problem_id); - mySociety::Alert::confirm($alert_id); + my $alert_id = FixMyStreet::Alert::create($email, 'new_updates', $cobrand, $cobrand_data, $problem_id); + FixMyStreet::Alert::confirm($alert_id); } return $out; @@ -182,8 +182,8 @@ $q->p('<a href="/report/' . $id . '">' . _('View your report') . '</a>.'); } # Subscribe problem reporter to email updates - my $alert_id = mySociety::Alert::create($email, 'new_updates', $cobrand, $cobrand_data, $id); - mySociety::Alert::confirm($alert_id); + my $alert_id = FixMyStreet::Alert::create($email, 'new_updates', $cobrand, $cobrand_data, $id); + FixMyStreet::Alert::confirm($alert_id); return $out; } diff --git a/web/contact.cgi b/web/contact.cgi index 0b1a27c9f..fd0043d94 100755 --- a/web/contact.cgi +++ b/web/contact.cgi @@ -19,7 +19,7 @@ use mySociety::Random qw(random_bytes); # Main code for index.cgi sub main { my $q = shift; - print Page::header($q, title=>_('Contact Us'), context=>'contact'); + print Page::header($q, title=>_('Contact Us'), context=>'contact', robots => 'noindex,nofollow'); my $out = ''; if ($q->param('submit_form')) { $out = contact_submit($q); diff --git a/web/import.cgi b/web/import.cgi index f839bd5f5..2aede1697 100755 --- a/web/import.cgi +++ b/web/import.cgi @@ -11,6 +11,7 @@ use strict; use Error qw(:try); use Standard; +use Utils; use mySociety::AuthToken; use mySociety::EmailUtil; use mySociety::EvEl; diff --git a/web/index.cgi b/web/index.cgi index dcbec5a1c..83b3b421c 100755 --- a/web/index.cgi +++ b/web/index.cgi @@ -8,6 +8,7 @@ use strict; use Standard; +use Utils; use Error qw(:try); use File::Slurp; use LWP::Simple; @@ -16,6 +17,7 @@ use CGI::Carp; use URI::Escape; use CrossSell; +use FixMyStreet::Geocode; use mySociety::AuthToken; use mySociety::Config; use mySociety::DBHandle qw(select_all); @@ -80,7 +82,7 @@ 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'))) { + } elsif ($q->param('pc') || ($q->param('x') && $q->param('y')) || ($q->param('e') && $q->param('n'))) { ($out, %params) = display_location($q); $params{title} = _('Viewing a location'); } else { @@ -457,50 +459,34 @@ sub display_form { return display_location($q, @errors) unless ($pin_x && $pin_y) || ($input{easting} && $input{northing}) - || ($input{skipped} && $input{x} && $input{y}) || ($input{skipped} && $input{pc}) || ($input{partial} && $input{pc}); # Work out some co-ordinates from whatever we've got - my ($px, $py, $easting, $northing); + my ($easting, $northing); if ($input{skipped}) { # Map is being skipped - if ($input{x} && $input{y}) { - $easting = Page::tile_to_os($input{x}); - $northing = Page::tile_to_os($input{y}); + if ($input{easting} && $input{northing}) { + $easting = $input{easting}; + $northing = $input{northing}; } else { - my ($x, $y, $e, $n, $error) = Page::geocode($input{pc}, $q); + my ($e, $n, $error) = FixMyStreet::Geocode::lookup($input{pc}, $q); $easting = $e; $northing = $n; } } elsif ($pin_x && $pin_y) { - # Map was clicked on - $pin_x = Page::click_to_tile($pin_tile_x, $pin_x); - $pin_y = Page::click_to_tile($pin_tile_y, $pin_y, 1); - $input{x} ||= int($pin_x); - $input{y} ||= int($pin_y); - $px = Page::tile_to_px($pin_x, $input{x}); - $py = Page::tile_to_px($pin_y, $input{y}, 1); - $easting = Page::tile_to_os($pin_x); - $northing = Page::tile_to_os($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}) { - my ($x, $y, $error); + my $error; try { - ($x, $y, $easting, $northing, $error) = Page::geocode($input{pc}, $q); + ($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(Page::os_to_tile($easting)); - $input{y} = int(Page::os_to_tile($northing)); - $px = Page::os_to_px($easting, $input{x}); - $py = Page::os_to_px($northing, $input{y}, 1); } else { # Normal form submission - my ($x, $y, $tile_x, $tile_y); - ($x, $y, $tile_x, $tile_y, $px, $py) = Page::os_to_px_with_adjust($q, $input{easting}, $input{northing}, undef, undef); - $input{x} = $tile_x; - $input{y} = $tile_y; $easting = $input_h{easting}; $northing = $input_h{northing}; } @@ -589,22 +575,22 @@ please specify the closest point on land.')) unless %$all_councils; $vars{form_start} = <<EOF; <form action="$form_action" method="post" name="mapSkippedForm"$enctype> <input type="hidden" name="pc" value="$input_h{pc}"> -<input type="hidden" name="x" value="$input_h{x}"> -<input type="hidden" name="y" value="$input_h{y}"> <input type="hidden" name="skipped" value="1"> $cobrand_form_elements <div id="skipped-map"> EOF } else { - my $pins = Page::display_pin($q, $px, $py, 'purple'); my $type; if ($allow_photo_upload) { $type = 2; } else { $type = 1; } - $vars{form_start} = Page::display_map($q, x => $input{x}, 'y' => $input{y}, type => $type, - pins => $pins, px => $px, py => $py ); + $vars{form_start} = FixMyStreet::Map::display_map($q, + easting => $easting, northing => $northing, + type => $type, + pins => [ [ $easting, $northing, 'purple' ] ], + ); my $partial_id; if (my $token = $input{partial}) { $partial_id = mySociety::AuthToken::retrieve('partial', $token); @@ -770,41 +756,43 @@ EOF %vars = (%vars, category => $category, - map_end => Page::display_map_end(1), + map_end => FixMyStreet::Map::display_map_end(1), url_home => Cobrand::url($cobrand, '/', $q), submit_button => _('Submit') ); - return (Page::template_include('report-form', $q, Page::template_root($q), %vars)); + return (Page::template_include('report-form', $q, Page::template_root($q), %vars), robots => 'noindex,nofollow'); } sub display_location { my ($q, @errors) = @_; my $cobrand = Page::get_cobrand($q); - my @vars = qw(pc x y all_pins no_pins); + 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; - if ($input{y} =~ /favicon/) { - my $base = mySociety::Config::get('BASE_URL'); - print $q->redirect(-location => $base . '/favicon.ico', -status => 301); - return ''; - } - my($error, $easting, $northing); + + (my $easting) = $input{e} =~ /^(\d+)/; $easting ||= 0; + (my $northing) = $input{n} =~ /^(\d+)/; $northing ||= 0; + + # 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}; - if (!$x && !$y) { + return front_page($q, @errors) unless $x || $y || $input{pc} || $easting || $northing; + + 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) { + # Don't need to do anything + } else { + my $error; try { - ($x, $y, $easting, $northing, $error) = Page::geocode($input{pc}, $q); + ($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 front_page($q, $error) if ($error); - - if (!$easting || !$northing) { - $easting = Page::tile_to_os($x); - $northing = Page::tile_to_os($y); + 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 @@ -821,16 +809,12 @@ sub display_location { $all_text = _('Include stale reports'); $interval = '6 months'; } - my ($pins, $on_map, $around_map, $dist) = Page::map_pins($q, $x, $y, $x, $y, $interval); - if ($input{no_pins}) { - $hide_link = NewURL($q, -retain=>1, no_pins=>undef); - $hide_text = _('Show pins'); - $pins = ''; - } else { - $hide_link = NewURL($q, -retain=>1, no_pins=>1); - $hide_text = _('Hide pins'); + + my ($on_map_all, $on_map, $around_map, $dist) = FixMyStreet::Map::map_features($q, $easting, $northing, $interval); + my @pins; + foreach (@$on_map_all) { + push @pins, [ $_->{easting}, $_->{northing}, $_->{state} eq 'fixed' ? 'green' : 'red' ]; } - my $map_links = "<p id='sub_map_links'><a id='hide_pins_link' rel='nofollow' href='$hide_link'>$hide_text</a> | <a id='all_pins_link' rel='nofollow' href='$all_link'>$all_text</a></p> <input type='hidden' id='all_pins' name='all_pins' value='$input_h{all_pins}'>"; my $on_list = ''; foreach (@$on_map) { my $report_url = NewURL($q, -retain => 1, -url => '/report/' . $_->{id}, pc => undef, x => undef, 'y' => undef); @@ -854,18 +838,38 @@ sub display_location { $around_list .= '</a>'; $around_list .= ' <small>' . _('(fixed)') . '</small>' if $_->{state} eq 'fixed'; $around_list .= '</li>'; + push @pins, [ $_->{easting}, $_->{northing}, $_->{state} eq 'fixed' ? 'green' : 'red' ]; } $around_list = $q->li(_('No problems found.')) unless $around_list; - my $url_skip = NewURL($q, -retain=>1, 'submit_map'=>1, skipped=>1); + if ($input{no_pins}) { + $hide_link = NewURL($q, -retain=>1, no_pins=>undef); + $hide_text = _('Show pins'); + @pins = (); + } else { + $hide_link = NewURL($q, -retain=>1, no_pins=>1); + $hide_text = _('Hide pins'); + } + my $map_links = "<p id='sub_map_links'><a id='hide_pins_link' rel='nofollow' href='$hide_link'>$hide_text</a> | <a id='all_pins_link' rel='nofollow' href='$all_link'>$all_text</a></p> <input type='hidden' id='all_pins' name='all_pins' value='$input_h{all_pins}'>"; + + my $url_skip = NewURL($q, -retain=>1, pc => undef, + x => undef, 'y' => undef, + easting => $easting, northing => $northing, + 'submit_map'=>1, skipped=>1 + ); my $pc_h = ent($q->param('pc') || ''); my %vars = ( - 'map' => Page::display_map($q, x => $x, 'y' => $y, type => 1, pins => $pins, post => $map_links ), - map_end => Page::display_map_end(1), + 'map' => FixMyStreet::Map::display_map($q, + easting => $easting, northing => $northing, + 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', x=>$x, 'y'=>$y, feed=>"local:$easting:$northing"), $q), + url_email => Cobrand::url($cobrand, NewURL($q, -retain => 1, pc => undef, -url=>'/alert', e=>$easting, 'n'=>$northing, feed=>"local:$easting:$northing"), $q), url_skip => $url_skip, email_me => _('Email me new local problems'), rss_alt => _('RSS feed'), @@ -885,7 +889,8 @@ sub display_location { ); my %params = ( - rss => [ _('Recent local problems, FixMyStreet'), "/rss/n/$easting,$northing" ] + rss => [ _('Recent local problems, FixMyStreet'), "/rss/n/$easting,$northing" ], + robots => 'noindex,nofollow', ); return (Page::template_include('map', $q, Page::template_root($q), %vars), %params); @@ -898,11 +903,9 @@ sub display_problem { my $cobrand = Page::get_cobrand($q); push @errors, _('There were problems with your update. Please see below.') if (scalar keys %field_errors); - my @vars = qw(id name rznvy update fixed add_alert upload_fileid x y submit_update); + my @vars = qw(id name rznvy update fixed add_alert upload_fileid submit_update); my %input = map { $_ => $q->param($_) || '' } @vars; my %input_h = map { $_ => $q->param($_) ? ent($q->param($_)) : '' } @vars; - ($input{x}) = $input{x} =~ /^(\d+)/; $input{x} ||= 0; - ($input{y}) = $input{y} =~ /^(\d+)/; $input{y} ||= 0; my $base = Cobrand::base_url($cobrand); # Some council with bad email software @@ -923,62 +926,70 @@ sub display_problem { my $problem = Problems::fetch_problem($input{id}); return display_location($q, _('Unknown problem ID')) unless $problem; return front_page($q, _('That report has been removed from FixMyStreet.'), '410 Gone') if $problem->{state} eq 'hidden'; - my ($x, $y, $x_tile, $y_tile, $px, $py) = Page::os_to_px_with_adjust($q, $problem->{easting}, $problem->{northing}, $input{x}, $input{y}); - - # Try and have pin near centre of map - if (!$input{x} && $x - $x_tile > 0.5) { - $x_tile += 1; - $px = Page::os_to_px($problem->{easting}, $x_tile); - } - if (!$input{y} && $y - $y_tile > 0.5) { - $y_tile += 1; - $py = Page::os_to_px($problem->{northing}, $y_tile, 1); - } - my %vars; 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 $pins = Page::display_pin($q, $px, $py, 'blue'); - $vars{map_start} = Page::display_map($q, x => $x_tile, 'y' => $y_tile, type => 0, - pins => $pins, px => $px, py => $py, post => $map_links ); + 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 $banner; if ($q->{site} ne 'emptyhomes' && $problem->{state} eq 'confirmed' && $problem->{duration} > 8*7*24*60*60) { - $vars{banner} = $q->p({id => 'unknown'}, _('This problem is old and of unknown status.')) + $banner = $q->p({id => 'unknown'}, _('This problem is old and of unknown status.')); } if ($problem->{state} eq 'fixed') { - $vars{banner} = $q->p({id => 'fixed'}, _('This problem has been fixed') . '.') + $banner = $q->p({id => 'fixed'}, _('This problem has been fixed') . '.'); } - $vars{problem_title} = ent($problem->{title}); - $vars{problem_meta} = Page::display_problem_meta_line($q, $problem); - $vars{problem_detail} = Page::display_problem_detail($problem); - $vars{problem_photo} = Page::display_problem_photo($q, $problem); - - my $contact_url = Cobrand::url($cobrand, NewURL($q, -retain => 1, pc => undef, -url=>'/contact?id=' . $input{id}), $q); - $vars{unsuitable} = $q->a({rel => 'nofollow', href => $contact_url}, _('Offensive? Unsuitable? Tell us')); - - my $back = Cobrand::url($cobrand, NewURL($q, -url => '/', 'x' => $x_tile, 'y' => $y_tile, -retain => 1, pc => undef, id => undef ), $q); - $vars{more_problems} = '<a href="' . $back . '">' . _('More problems nearby') . '</a>'; + 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}), + -retain => 1, pc => undef, x => undef, 'y' => undef, id => undef + ), $q); + my $fixed = ($input{fixed}) ? ' checked' : ''; - $vars{url_home} = Cobrand::url($cobrand, '/', $q), + my %vars = ( + banner => $banner, + map_start => FixMyStreet::Map::display_map($q, + easting => $problem->{easting}, northing => $problem->{northing}, + type => 0, + pins => [ [ $problem->{easting}, $problem->{northing}, 'blue' ] ], + post => $map_links + ), + map_end => FixMyStreet::Map::display_map_end(0), + problem_title => ent($problem->{title}), + problem_meta => Page::display_problem_meta_line($q, $problem), + problem_detail => Page::display_problem_detail($problem), + problem_photo => Page::display_problem_photo($q, $problem), + problem_updates => Page::display_problem_updates($input{id}, $q), + unsuitable => $q->a({rel => 'nofollow', href => $contact_url}, _('Offensive? Unsuitable? Tell us')), + more_problems => '<a href="' . $back . '">' . _('More problems nearby') . '</a>', + url_home => Cobrand::url($cobrand, '/', $q), + alert_link => Cobrand::url($cobrand, NewURL($q, -url => '/alert?type=updates;id='.$input_h{id}, -retain => 1, pc => undef, x => undef, 'y' => undef ), $q), + alert_text => _('Email me updates'), + email_label => _('Email:'), + subscribe => _('Subscribe'), + blurb => _('Receive email when updates are left on this problem'), + cobrand_form_elements1 => Cobrand::form_elements($cobrand, 'alerts', $q), + form_alert_action => Cobrand::url($cobrand, '/alert', $q), + rss_url => Cobrand::url($cobrand, NewURL($q, -retain=>1, -url => '/rss/'.$input_h{id}, pc => undef, x => undef, 'y' => undef, id => undef), $q), + rss_title => _('RSS feed'), + rss_alt => _('RSS feed of updates to this problem'), + update_heading => $q->h2(_('Provide an update')), + field_errors => \%field_errors, + add_alert_checked => ($input{add_alert} || !$input{submit_update}) ? ' checked' : '', + fixedline_box => $problem->{state} eq 'fixed' ? '' : qq{<input type="checkbox" name="fixed" id="form_fixed" value="1"$fixed>}, + fixedline_label => $problem->{state} eq 'fixed' ? '' : qq{<label for="form_fixed">} . _('This problem has been fixed') . qq{</label>}, + name_label => _('Name:'), + update_label => _('Update:'), + alert_label => _('Alert me to future updates'), + post_label => _('Post'), + cobrand_form_elements => Cobrand::form_elements($cobrand, 'updateForm', $q), + form_action => Cobrand::url($cobrand, '/', $q), + input_h => \%input_h, + ); - $vars{alert_link} = Cobrand::url($cobrand, NewURL($q, -url => '/alert?type=updates;id='.$input_h{id}, -retain => 1, pc => undef ), $q); - $vars{alert_text} = _('Email me updates'); - $vars{email_label} = _('Email:'); - $vars{subscribe} = _('Subscribe'); - $vars{blurb} = _('Receive email when updates are left on this problem'); - $vars{cobrand_form_elements1} = Cobrand::form_elements($cobrand, 'alerts', $q); - $vars{form_alert_action} = Cobrand::url($cobrand, '/alert', $q); - $vars{rss_url} = Cobrand::url($cobrand, NewURL($q, -retain=>1, -url => '/rss/'.$input_h{id}, pc => undef, id => undef), $q); - $vars{rss_title} = _('RSS feed'); - $vars{rss_alt} = _('RSS feed of updates to this problem'); - - $vars{problem_updates} = Page::display_problem_updates($input{id}, $q); - $vars{update_heading} = $q->h2(_('Provide an update')); $vars{update_blurb} = $q->p($q->small(_('Please note that updates are not sent to the council. If you leave your name it will be public. Your information will only be used in accordance with our <a href="/faq#privacy">privacy policy</a>'))) unless $q->{site} eq 'emptyhomes'; # No council blurb @@ -986,22 +997,10 @@ sub display_problem { $vars{errors} = '<ul class="error"><li>' . join('</li><li>', @errors) . '</li></ul>'; } - $vars{field_errors} = \%field_errors; - - my $fixed = ($input{fixed}) ? ' checked' : ''; - $vars{add_alert_checked} = ($input{add_alert} || !$input{submit_update}) ? ' checked' : ''; - $vars{fixedline_box} = $problem->{state} eq 'fixed' ? '' - : qq{<input type="checkbox" name="fixed" id="form_fixed" value="1"$fixed>}; - $vars{fixedline_label} = $problem->{state} eq 'fixed' ? '' - : qq{<label for="form_fixed">} . _('This problem has been fixed') . qq{</label>}; - $vars{name_label} = _('Name:'); - $vars{update_label} = _('Update:'); - $vars{alert_label} = _('Alert me to future updates'); - $vars{post_label} = _('Post'); - $vars{cobrand_form_elements} = Cobrand::form_elements($cobrand, 'updateForm', $q); my $allow_photo_upload = Cobrand::allow_photo_upload($cobrand); if ($allow_photo_upload) { my $photo_label = _('Photo:'); + $vars{enctype} = ' enctype="multipart/form-data"'; $vars{photo_element} = <<EOF; <div id="fileupload_normalUI"> <label for="form_photo">$photo_label</label> @@ -1010,17 +1009,12 @@ sub display_problem { EOF } - $vars{form_action} = Cobrand::url($cobrand, '/', $q); - if ($allow_photo_upload) { - $vars{enctype} = ' enctype="multipart/form-data"'; - } - $vars{map_end} = Page::display_map_end(0); my %params = ( rss => [ _('Updates to this problem, FixMyStreet'), "/rss/$input_h{id}" ], + robots => 'index, nofollow', title => $problem->{title} ); - $vars{input_h} = \%input_h; my $page = Page::template_include('problem', $q, Page::template_root($q), %vars); return ($page, %params); } @@ -1,12 +1,6 @@ /* * js.js * FixMyStreet JavaScript - * - * TODO - * Investigate jQuery - * Tidy it all up - * Selection of pin doesn't really need a server request, but I don't really care - * */ @@ -16,41 +10,6 @@ YAHOO.util.Event.onContentReady('pc', function() { } }); -function compass_pan(e, a) { - YAHOO.util.Event.preventDefault(e); - if (a.home) { - a.x = a.orig_x-drag_x; - a.y = a.orig_y-drag_y; - } - pan(a.x, a.y); -} - -YAHOO.util.Event.onContentReady('compass', function() { - var ua=navigator.userAgent.toLowerCase(); - // if (document.getElementById('mapForm') && (/safari/.test(ua) || /Konqueror/.test(ua))) return; - if (document.getElementById('map').offsetWidth > 510) return; - - var points = this.getElementsByTagName('a'); - YAHOO.util.Event.addListener(points[1], 'click', compass_pan, { x:0, y:fixmystreet.tileheight }); - YAHOO.util.Event.addListener(points[3], 'click', compass_pan, { x:fixmystreet.tilewidth, y:0 }); - YAHOO.util.Event.addListener(points[5], 'click', compass_pan, { x:-fixmystreet.tilewidth, y:0 }); - YAHOO.util.Event.addListener(points[7], 'click', compass_pan, { x:0, y:-fixmystreet.tileheight }); - YAHOO.util.Event.addListener(points[0], 'click', compass_pan, { x:fixmystreet.tilewidth, y:fixmystreet.tileheight }); - YAHOO.util.Event.addListener(points[2], 'click', compass_pan, { x:-fixmystreet.tilewidth, y:fixmystreet.tileheight }); - YAHOO.util.Event.addListener(points[6], 'click', compass_pan, { x:fixmystreet.tilewidth, y:-fixmystreet.tileheight }); - YAHOO.util.Event.addListener(points[8], 'click', compass_pan, { x:-fixmystreet.tilewidth, y:-fixmystreet.tileheight }); - YAHOO.util.Event.addListener(points[4], 'click', compass_pan, { home:1, orig_x:drag_x, orig_y:drag_y }); -}); - -YAHOO.util.Event.onContentReady('map', function() { - var ua=navigator.userAgent.toLowerCase(); - // if (document.getElementById('mapForm') && (/safari/.test(ua) || /Konqueror/.test(ua))) return; - if (document.getElementById('map').offsetWidth > 510) return; - new YAHOO.util.DDMap('map'); - update_tiles(fixmystreet.start_x, fixmystreet.start_y, true); -}); - - YAHOO.util.Event.onContentReady('mapForm', function() { this.onsubmit = function() { if (this.submit_problem) { @@ -119,52 +78,6 @@ YAHOO.util.Event.onContentReady('email_alert_box', function() { }); }); -YAHOO.util.Event.addListener('hide_pins_link', 'click', function(e) { - YAHOO.util.Event.preventDefault(e); - if (this.innerHTML == 'Show pins') { - YAHOO.util.Dom.setStyle('pins', 'display', 'block'); - this.innerHTML = 'Hide pins'; - } else if (this.innerHTML == 'Dangos pinnau') { - YAHOO.util.Dom.setStyle('pins', 'display', 'block'); - this.innerHTML = 'Cuddio pinnau'; - } else if (this.innerHTML == 'Cuddio pinnau') { - YAHOO.util.Dom.setStyle('pins', 'display', 'none'); - this.innerHTML = 'Dangos pinnau'; - } else if (this.innerHTML == 'Hide pins') { - YAHOO.util.Dom.setStyle('pins', 'display', 'none'); - this.innerHTML = 'Show pins'; - } -}); -YAHOO.util.Event.addListener('all_pins_link', 'click', function(e) { - YAHOO.util.Event.preventDefault(e); - YAHOO.util.Dom.setStyle('pins', 'display', 'block'); - var welsh = 0; - if (this.innerHTML == 'Include stale reports') { - this.innerHTML = 'Hide stale reports'; - document.getElementById('all_pins').value = '1'; - load_pins(fixmystreet.x, fixmystreet.y); - } else if (this.innerHTML == 'Cynnwys hen adroddiadau') { - this.innerHTML = 'Cuddio hen adroddiadau'; - document.getElementById('all_pins').value = '1'; - welsh = 1; - load_pins(fixmystreet.x, fixmystreet.y); - } else if (this.innerHTML == 'Cuddio hen adroddiadau') { - this.innerHTML = 'Cynnwys hen adroddiadau'; - welsh = 1; - document.getElementById('all_pins').value = ''; - load_pins(fixmystreet.x, fixmystreet.y); - } else if (this.innerHTML == 'Hide stale reports') { - this.innerHTML = 'Include stale reports'; - document.getElementById('all_pins').value = ''; - load_pins(fixmystreet.x, fixmystreet.y); - } - if (welsh) { - document.getElementById('hide_pins_link').innerHTML = 'Cuddio pinnau'; - } else { - document.getElementById('hide_pins_link').innerHTML = 'Hide pins'; - } -}); - /* File upload */ /* function doSubmit(e) { @@ -250,238 +163,3 @@ var swfu_settings = { } }; */ - -// I love the global -var tile_x = 0; -var tile_y = 0; - -var myAnim; -function pan(x, y) { - if (!myAnim || !myAnim.isAnimated()) { - myAnim = new YAHOO.util.Motion('drag', { points:{by:[x,y]} }, 10, YAHOO.util.Easing.easeOut); - myAnim.useSeconds = false; - //myAnim.onTween.subscribe(function(){ update_tiles(x/10, y/10, false); }); - myAnim.onComplete.subscribe(function(){ - update_tiles(x, y, false); - cleanCache(); - }); - myAnim.animate(); - } -} - -var drag_x = 0; -var drag_y = 0; -function update_tiles(dx, dy, force) { - dx = getInt(dx); dy = getInt(dy); - if (!dx && !dy && !force) return; - var old_drag_x = drag_x; - var old_drag_y = drag_y; - drag_x += dx; - drag_y += dy; - - var drag = document.getElementById('drag'); - drag.style.left = drag_x + 'px'; - drag.style.top = drag_y + 'px'; - - var horizontal = Math.floor(old_drag_x/fixmystreet.tilewidth) - Math.floor(drag_x/fixmystreet.tilewidth); - var vertical = Math.floor(old_drag_y/fixmystreet.tileheight) - Math.floor(drag_y/fixmystreet.tileheight); - if (!horizontal && !vertical && !force) return; - fixmystreet.x += horizontal; - - tile_x += horizontal; - fixmystreet.y -= vertical; - tile_y += vertical; - var url = [ root_path + '/tilma/tileserver/' + fixmystreet.tile_type + '/', fixmystreet.x, '-', (fixmystreet.x+5), ',', fixmystreet.y, '-', (fixmystreet.y+5), '/JSON' ].join(''); - YAHOO.util.Connect.asyncRequest('GET', url, { - success: urls_loaded, failure: urls_not_loaded, - argument: [tile_x, tile_y] - }); - - if (force) return; - load_pins(fixmystreet.x, fixmystreet.y); -} - -function load_pins(x, y) { - if (document.getElementById('formX')) { - all_pins = ''; - if (document.getElementById('all_pins')) { - all_pins = document.getElementById('all_pins').value; - } - var ajax_params = [ 'sx=' + document.getElementById('formX').value, - 'sy=' + document.getElementById('formY').value, - 'x=' + (x+3), - 'y=' + (y+3), - 'all_pins=' + all_pins ]; - - if (document.getElementById('extra_param')) { - ajax_params.push(document.getElementById('extra_param').name + '=' + document.getElementById('extra_param').value); - } - - var url = [ root_path , '/ajax?', ajax_params.join(';')].join(''); - YAHOO.util.Connect.asyncRequest('GET', url, { - success: pins_loaded - }); - } -} - -function pins_loaded(o) { - var data = eval(o.responseText); - document.getElementById('pins').innerHTML = data.pins; - if (typeof(data.current) != 'undefined') - document.getElementById('current').innerHTML = data.current; - if (typeof(data.current_near) != 'undefined') - document.getElementById('current_near').innerHTML = data.current_near; - if (typeof(data.fixed_near) != 'undefined') - document.getElementById('fixed_near').innerHTML = data.fixed_near; -} - -function urls_not_loaded(o) { /* Nothing yet */ } - -// Load 6x6 grid of tiles around current 2x2 -function urls_loaded(o) { - var tiles = eval(o.responseText); - var drag = document.getElementById('drag'); - for (var i=0; i<6; i++) { - var ii = (i + o.argument[1]); - for (var j=0; j<6; j++) { - if (tiles[i][j] == null) continue; - var jj = (j + o.argument[0]); - var id = [ 't', ii, '.', jj ].join(''); - var xx = fixmystreet.x+j; - var yy = fixmystreet.y+5-i; - var img = document.getElementById(id); - if (img) { - if (!img.galleryimg) { img.galleryimg = false; } - img.onclick = drag_check; - tileCache[id] = { x: xx, y: yy, t: img }; - continue; - } - img = cloneNode(); - img.style.top = ((ii-2)*fixmystreet.tileheight) + 'px'; - img.style.left = ((jj-2)*fixmystreet.tilewidth) + 'px'; - img.name = [ 'tile_', xx, '.', yy ].join('') - img.id = id; - if (browser) { - img.style.visibility = 'hidden'; - img.onload=function() { this.style.visibility = 'visible'; } - } - img.src = 'http://tilma.mysociety.org/tileserver/' + fixmystreet.tile_type + '/' + tiles[i][j]; - tileCache[id] = { x: xx, y: yy, t: img }; - drag.appendChild(img); - } - } -} - -var imgElCache; -function cloneNode() { - var img = null; - if (!imgElCache) { - var form = document.getElementById('mapForm'); - if (form) { - img = imgElCache = document.createElement('input'); - img.type = 'image'; - } else { - img = imgElCache = document.createElement('img'); - } - img.onclick = drag_check; - img.style.position = 'absolute'; - img.style.width = fixmystreet.tilewidth + 'px'; - img.style.height = fixmystreet.tileheight + 'px'; - img.galleryimg = false; - img.alt = 'Loading...'; - } else { - img = imgElCache.cloneNode(true); - } - return img; -} - -var tileCache=[]; -function cleanCache() { - for (var i in tileCache) { - if (tileCache[i].x < fixmystreet.x || tileCache[i].x > fixmystreet.x+5 || tileCache[i].y < fixmystreet.y || tileCache[i].y > fixmystreet.y+5) { - var t = tileCache[i].t; - t.parentNode.removeChild(t); // de-leak? - delete tileCache[i]; - } - } -} - -/* Called every mousemove, so on first call, overwrite itself with quicker version */ -function get_posn(ev) { - var posx, posy; - if (ev.pageX || ev.pageY) { - get_posn = function(e) { - return { x: e.pageX, y: e.pageY }; - }; - } else if (ev.clientX || ev.clientY) { - get_posn = function(e) { - return { - x: e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft, - y: e.clientY + document.body.scrollTop + document.documentElement.scrollTop - }; - }; - } else { - get_posn = function(e) { - return { x: undef, y: undef }; - }; - } - return get_posn(ev); -} - -function setCursor(s) { - var drag = document.getElementById('drag'); - var inputs = drag.getElementsByTagName('input'); - for (var i=0; i<inputs.length; i++) { - inputs[i].style.cursor = s; - } -} - -var in_drag = false; -function drag_check(e) { - if (in_drag) { - in_drag = false; - return false; - } - return true; -} - -/* Simpler version of DDProxy */ -var mouse_pos = {}; -YAHOO.util.DDMap = function(id, sGroup, config) { - this.init(id, sGroup, config); -}; -YAHOO.extend(YAHOO.util.DDMap, YAHOO.util.DD, { - scroll: false, - b4MouseDown: function(e) { }, - startDrag: function(x, y) { - mouse_pos = { x: x, y: y }; - setCursor('move'); - in_drag = true; - }, - b4Drag: function(e) { }, - onDrag: function(e) { - var point = get_posn(e); - if (point == mouse_pos) return false; - var dx = point.x-mouse_pos.x; - var dy = point.y-mouse_pos.y; - mouse_pos = point; - update_tiles(dx, dy, false); - }, - endDrag: function(e) { - setCursor('crosshair'); - cleanCache(); - }, - toString: function() { - return ("DDMap " + this.id); - } -}); - -var browser = 1; -var ua=navigator.userAgent.toLowerCase(); -if (!/opera|safari|gecko/.test(ua) && typeof document.all!='undefined') - browser=0; - -function getInt(n) { - n = parseInt(n); return (isNaN(n) ? 0 : n); -} - diff --git a/web/js/OpenLayers.Projection.OrdnanceSurvey.js b/web/js/OpenLayers.Projection.OrdnanceSurvey.js new file mode 100644 index 000000000..bb596d3bf --- /dev/null +++ b/web/js/OpenLayers.Projection.OrdnanceSurvey.js @@ -0,0 +1,489 @@ +/** + * OpenLayers OSGB Grid Projection Transformations + * + * Conversion to OpenLayers by Thomas Wood (grand.edgemaster@gmail.com) + * + * this program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * this program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * --------------------------------------------------------------------------- + * + * PLEASE DO NOT HOTLINK THIS, save this onto your own server + * - I cannot guarantee this file will remain here forever. + * + * --------------------------------------------------------------------------- + * + * Credits: + * Based from the geotools js library by Paul Dixon + * GeoTools javascript coordinate transformations + * http://files.dixo.net/geotools.html + * + * Portions of this file copyright (c)2005 Paul Dixon (paul@elphin.com) + * + * The algorithm used by the script for WGS84-OSGB36 conversions is derived + * from an OSGB spreadsheet (www.gps.gov.uk) with permission. This has been + * adapted into Perl by Ian Harris, and into PHP by Barry Hunter. Conversion + * accuracy is in the order of 7m for 90% of Great Britain, and should be + * be similar to the conversion made by a typical GPSr + * + */ + +OpenLayers.Projection.OS = { + + /** + * Method: projectForwardBritish + * Given an object with x and y properties in EPSG:4326, modify the x,y + * properties on the object to be the OSGB36 (transverse mercator) + * projected coordinates. + * + * Parameters: + * point - {Object} An object with x and y properties. + * + * Returns: + * {Object} The point, with the x and y properties transformed to spherical + * mercator. + */ + projectForwardBritish: function(point) { + var x1 = OpenLayers.Projection.OS.Lat_Long_H_to_X(point.y,point.x,0,6378137.00,6356752.313); + var y1 = OpenLayers.Projection.OS.Lat_Long_H_to_Y(point.y,point.x,0,6378137.00,6356752.313); + var z1 = OpenLayers.Projection.OS.Lat_H_to_Z (point.y, 0,6378137.00,6356752.313); + + var x2 = OpenLayers.Projection.OS.Helmert_X(x1,y1,z1,-446.448,-0.2470,-0.8421,20.4894); + var y2 = OpenLayers.Projection.OS.Helmert_Y(x1,y1,z1, 125.157,-0.1502,-0.8421,20.4894); + var z2 = OpenLayers.Projection.OS.Helmert_Z(x1,y1,z1,-542.060,-0.1502,-0.2470,20.4894); + + var lat2 = OpenLayers.Projection.OS.XYZ_to_Lat (x2,y2,z2,6377563.396,6356256.910); + var lon2 = OpenLayers.Projection.OS.XYZ_to_Long(x2,y2); + + point.x = OpenLayers.Projection.OS.Lat_Long_to_East (lat2,lon2,6377563.396,6356256.910,400000,0.999601272,49.00000,-2.00000); + point.y = OpenLayers.Projection.OS.Lat_Long_to_North(lat2,lon2,6377563.396,6356256.910,400000,-100000,0.999601272,49.00000,-2.00000); + + return point; + }, + + /** + * Method: projectInverseBritish + * Given an object with x and y properties in OSGB36 (transverse mercator), + * modify the x,y properties on the object to be the unprojected coordinates. + * + * Parameters: + * point - {Object} An object with x and y properties. + * + * Returns: + * {Object} The point, with the x and y properties transformed from + * OSGB36 to unprojected coordinates.. + */ + projectInverseBritish: function(point) { + var lat1 = OpenLayers.Projection.OS.E_N_to_Lat (point.x,point.y,6377563.396,6356256.910,400000,-100000,0.999601272,49.00000,-2.00000); + var lon1 = OpenLayers.Projection.OS.E_N_to_Long(point.x,point.y,6377563.396,6356256.910,400000,-100000,0.999601272,49.00000,-2.00000); + + var x1 = OpenLayers.Projection.OS.Lat_Long_H_to_X(lat1,lon1,0,6377563.396,6356256.910); + var y1 = OpenLayers.Projection.OS.Lat_Long_H_to_Y(lat1,lon1,0,6377563.396,6356256.910); + var z1 = OpenLayers.Projection.OS.Lat_H_to_Z (lat1, 0,6377563.396,6356256.910); + + var x2 = OpenLayers.Projection.OS.Helmert_X(x1,y1,z1,446.448 ,0.2470,0.8421,-20.4894); + var y2 = OpenLayers.Projection.OS.Helmert_Y(x1,y1,z1,-125.157,0.1502,0.8421,-20.4894); + var z2 = OpenLayers.Projection.OS.Helmert_Z(x1,y1,z1,542.060 ,0.1502,0.2470,-20.4894); + + var lat = OpenLayers.Projection.OS.XYZ_to_Lat(x2,y2,z2,6378137.000,6356752.313); + var lon = OpenLayers.Projection.OS.XYZ_to_Long(x2,y2); + + point.x = lon; + point.y = lat; + return point; + }, + + goog2osgb: function(point) { + return OpenLayers.Projection.OS.projectForwardBritish(OpenLayers.Layer.SphericalMercator.projectInverse(point)); + }, + + osgb2goog: function(point) { + return OpenLayers.Layer.SphericalMercator.projectForward(OpenLayers.Projection.OS.projectInverseBritish(point)); + }, + + /***** + * Mathematical functions + *****/ + E_N_to_Lat: function(East, North, a, b, e0, n0, f0, PHI0, LAM0) { + //Un-project Transverse Mercator eastings and northings back to latitude. + //eastings (East) and northings (North) in meters; _ + //ellipsoid axis dimensions (a & b) in meters; _ + //eastings (e0) and northings (n0) of false origin in meters; _ + //central meridian scale factor (f0) and _ + //latitude (PHI0) and longitude (LAM0) of false origin in decimal degrees. + + //Convert angle measures to radians + var Pi = 3.14159265358979; + var RadPHI0 = PHI0 * (Pi / 180); + var RadLAM0 = LAM0 * (Pi / 180); + + //Compute af0, bf0, e squared (e2), n and Et + var af0 = a * f0; + var bf0 = b * f0; + var e2 = (Math.pow(af0,2) - Math.pow(bf0,2)) / Math.pow(af0,2); + var n = (af0 - bf0) / (af0 + bf0); + var Et = East - e0; + + //Compute initial value for latitude (PHI) in radians + var PHId = OpenLayers.Projection.OS.InitialLat(North, n0, af0, RadPHI0, n, bf0); + + //Compute nu, rho and eta2 using value for PHId + var nu = af0 / (Math.sqrt(1 - (e2 * ( Math.pow(Math.sin(PHId),2))))); + var rho = (nu * (1 - e2)) / (1 - (e2 * Math.pow(Math.sin(PHId),2))); + var eta2 = (nu / rho) - 1; + + //Compute Latitude + var VII = (Math.tan(PHId)) / (2 * rho * nu); + var VIII = ((Math.tan(PHId)) / (24 * rho * Math.pow(nu,3))) * (5 + (3 * (Math.pow(Math.tan(PHId),2))) + eta2 - (9 * eta2 * (Math.pow(Math.tan(PHId),2)))); + var IX = ((Math.tan(PHId)) / (720 * rho * Math.pow(nu,5))) * (61 + (90 * ((Math.tan(PHId)) ^ 2)) + (45 * (Math.pow(Math.tan(PHId),4)))); + + var E_N_to_Lat = (180 / Pi) * (PHId - (Math.pow(Et,2) * VII) + (Math.pow(Et,4) * VIII) - ((Et ^ 6) * IX)); + + return (E_N_to_Lat); + }, + + E_N_to_Long: function(East, North, a, b, e0, n0, f0, PHI0, LAM0) { + //Un-project Transverse Mercator eastings and northings back to longitude. + //eastings (East) and northings (North) in meters; _ + //ellipsoid axis dimensions (a & b) in meters; _ + //eastings (e0) and northings (n0) of false origin in meters; _ + //central meridian scale factor (f0) and _ + //latitude (PHI0) and longitude (LAM0) of false origin in decimal degrees. + + //Convert angle measures to radians + var Pi = 3.14159265358979; + var RadPHI0 = PHI0 * (Pi / 180); + var RadLAM0 = LAM0 * (Pi / 180); + + //Compute af0, bf0, e squared (e2), n and Et + var af0 = a * f0; + var bf0 = b * f0; + var e2 = (Math.pow(af0,2) - Math.pow(bf0,2)) / Math.pow(af0,2); + var n = (af0 - bf0) / (af0 + bf0); + var Et = East - e0; + + //Compute initial value for latitude (PHI) in radians + var PHId = OpenLayers.Projection.OS.InitialLat(North, n0, af0, RadPHI0, n, bf0); + + //Compute nu, rho and eta2 using value for PHId + var nu = af0 / (Math.sqrt(1 - (e2 * (Math.pow(Math.sin(PHId),2))))); + var rho = (nu * (1 - e2)) / (1 - (e2 * Math.pow(Math.sin(PHId),2))); + var eta2 = (nu / rho) - 1; + + //Compute Longitude + var X = (Math.pow(Math.cos(PHId),-1)) / nu; + var XI = ((Math.pow(Math.cos(PHId),-1)) / (6 * Math.pow(nu,3))) * ((nu / rho) + (2 * (Math.pow(Math.tan(PHId),2)))); + var XII = ((Math.pow(Math.cos(PHId),-1)) / (120 * Math.pow(nu,5))) * (5 + (28 * (Math.pow(Math.tan(PHId),2))) + (24 * (Math.pow(Math.tan(PHId),4)))); + var XIIA = ((Math.pow(Math.cos(PHId),-1)) / (5040 * Math.pow(nu,7))) * (61 + (662 * (Math.pow(Math.tan(PHId),2))) + (1320 * (Math.pow(Math.tan(PHId),4))) + (720 * (Math.pow(Math.tan(PHId),6)))); + + var E_N_to_Long = (180 / Pi) * (RadLAM0 + (Et * X) - (Math.pow(Et,3) * XI) + (Math.pow(Et,5) * XII) - (Math.pow(Et,7) * XIIA)); + + return E_N_to_Long; + }, + + InitialLat: function(North, n0, afo, PHI0, n, bfo) { + //Compute initial value for Latitude (PHI) IN RADIANS. + //northing of point (North) and northing of false origin (n0) in meters; _ + //semi major axis multiplied by central meridian scale factor (af0) in meters; _ + //latitude of false origin (PHI0) IN RADIANS; _ + //n (computed from a, b and f0) and _ + //ellipsoid semi major axis multiplied by central meridian scale factor (bf0) in meters. + + //First PHI value (PHI1) + var PHI1 = ((North - n0) / afo) + PHI0; + + //Calculate M + var M = OpenLayers.Projection.OS.Marc(bfo, n, PHI0, PHI1); + + //Calculate new PHI value (PHI2) + var PHI2 = ((North - n0 - M) / afo) + PHI1; + + //Iterate to get final value for InitialLat + while (Math.abs(North - n0 - M) > 0.00001) + { + PHI2 = ((North - n0 - M) / afo) + PHI1; + M = OpenLayers.Projection.OS.Marc(bfo, n, PHI0, PHI2); + PHI1 = PHI2; + } + return PHI2; + }, + + Lat_Long_H_to_X: function(PHI, LAM, H, a, b) { + // Convert geodetic coords lat (PHI), long (LAM) and height (H) to cartesian X coordinate. + // Input: - _ + // Latitude (PHI)& Longitude (LAM) both in decimal degrees; _ + // Ellipsoidal height (H) and ellipsoid axis dimensions (a & b) all in meters. + + // Convert angle measures to radians + var Pi = 3.14159265358979; + var RadPHI = PHI * (Pi / 180); + var RadLAM = LAM * (Pi / 180); + + // Compute eccentricity squared and nu + var e2 = (Math.pow(a,2) - Math.pow(b,2)) / Math.pow(a,2); + var V = a / (Math.sqrt(1 - (e2 * ( Math.pow(Math.sin(RadPHI),2))))); + + // Compute X + return (V + H) * (Math.cos(RadPHI)) * (Math.cos(RadLAM)); + }, + + + Lat_Long_H_to_Y: function(PHI, LAM, H, a, b) { + // Convert geodetic coords lat (PHI), long (LAM) and height (H) to cartesian Y coordinate. + // Input: - _ + // Latitude (PHI)& Longitude (LAM) both in decimal degrees; _ + // Ellipsoidal height (H) and ellipsoid axis dimensions (a & b) all in meters. + + // Convert angle measures to radians + var Pi = 3.14159265358979; + var RadPHI = PHI * (Pi / 180); + var RadLAM = LAM * (Pi / 180); + + // Compute eccentricity squared and nu + var e2 = (Math.pow(a,2) - Math.pow(b,2)) / Math.pow(a,2); + var V = a / (Math.sqrt(1 - (e2 * ( Math.pow(Math.sin(RadPHI),2))) )); + + // Compute Y + return (V + H) * (Math.cos(RadPHI)) * (Math.sin(RadLAM)); + }, + + + Lat_H_to_Z: function(PHI, H, a, b) { + // Convert geodetic coord components latitude (PHI) and height (H) to cartesian Z coordinate. + // Input: - _ + // Latitude (PHI) decimal degrees; _ + // Ellipsoidal height (H) and ellipsoid axis dimensions (a & b) all in meters. + + // Convert angle measures to radians + var Pi = 3.14159265358979; + var RadPHI = PHI * (Pi / 180); + + // Compute eccentricity squared and nu + var e2 = (Math.pow(a,2) - Math.pow(b,2)) / Math.pow(a,2); + var V = a / (Math.sqrt(1 - (e2 * ( Math.pow(Math.sin(RadPHI),2)) ))); + + // Compute X + return ((V * (1 - e2)) + H) * (Math.sin(RadPHI)); + }, + + + Helmert_X: function(X,Y,Z,DX,Y_Rot,Z_Rot,s) { + + // (X, Y, Z, DX, Y_Rot, Z_Rot, s) + // Computed Helmert transformed X coordinate. + // Input: - _ + // cartesian XYZ coords (X,Y,Z), X translation (DX) all in meters ; _ + // Y and Z rotations in seconds of arc (Y_Rot, Z_Rot) and scale in ppm (s). + + // Convert rotations to radians and ppm scale to a factor + var Pi = 3.14159265358979; + var sfactor = s * 0.000001; + + var RadY_Rot = (Y_Rot / 3600) * (Pi / 180); + + var RadZ_Rot = (Z_Rot / 3600) * (Pi / 180); + + //Compute transformed X coord + return (X + (X * sfactor) - (Y * RadZ_Rot) + (Z * RadY_Rot) + DX); + }, + + + Helmert_Y: function(X,Y,Z,DY,X_Rot,Z_Rot,s) { + // Computed Helmert transformed Y coordinate. + // Input: - _ + // cartesian XYZ coords (X,Y,Z), Y translation (DY) all in meters ; _ + // X and Z rotations in seconds of arc (X_Rot, Z_Rot) and scale in ppm (s). + + // Convert rotations to radians and ppm scale to a factor + var Pi = 3.14159265358979; + var sfactor = s * 0.000001; + var RadX_Rot = (X_Rot / 3600) * (Pi / 180); + var RadZ_Rot = (Z_Rot / 3600) * (Pi / 180); + + // Compute transformed Y coord + return (X * RadZ_Rot) + Y + (Y * sfactor) - (Z * RadX_Rot) + DY; + }, + + + + Helmert_Z: function(X, Y, Z, DZ, X_Rot, Y_Rot, s) { + // Computed Helmert transformed Z coordinate. + // Input: - _ + // cartesian XYZ coords (X,Y,Z), Z translation (DZ) all in meters ; _ + // X and Y rotations in seconds of arc (X_Rot, Y_Rot) and scale in ppm (s). + // + // Convert rotations to radians and ppm scale to a factor + var Pi = 3.14159265358979; + var sfactor = s * 0.000001; + var RadX_Rot = (X_Rot / 3600) * (Pi / 180); + var RadY_Rot = (Y_Rot / 3600) * (Pi / 180); + + // Compute transformed Z coord + return (-1 * X * RadY_Rot) + (Y * RadX_Rot) + Z + (Z * sfactor) + DZ; + } , + + XYZ_to_Lat: function(X, Y, Z, a, b) { + // Convert XYZ to Latitude (PHI) in Dec Degrees. + // Input: - _ + // XYZ cartesian coords (X,Y,Z) and ellipsoid axis dimensions (a & b), all in meters. + + // this FUNCTION REQUIRES THE "Iterate_XYZ_to_Lat" FUNCTION + // this FUNCTION IS CALLED BY THE "XYZ_to_H" FUNCTION + + var RootXYSqr = Math.sqrt(Math.pow(X,2) + Math.pow(Y,2)); + var e2 = (Math.pow(a,2) - Math.pow(b,2)) / Math.pow(a,2); + var PHI1 = Math.atan2(Z , (RootXYSqr * (1 - e2)) ); + + var PHI = OpenLayers.Projection.OS.Iterate_XYZ_to_Lat(a, e2, PHI1, Z, RootXYSqr); + + var Pi = 3.14159265358979; + + return PHI * (180 / Pi); + }, + + + Iterate_XYZ_to_Lat: function(a, e2, PHI1, Z, RootXYSqr) { + // Iteratively computes Latitude (PHI). + // Input: - _ + // ellipsoid semi major axis (a) in meters; _ + // eta squared (e2); _ + // estimated value for latitude (PHI1) in radians; _ + // cartesian Z coordinate (Z) in meters; _ + // RootXYSqr computed from X & Y in meters. + + // this FUNCTION IS CALLED BY THE "XYZ_to_PHI" FUNCTION + // this FUNCTION IS ALSO USED ON IT'S OWN IN THE _ + // "Projection and Transformation Calculations.xls" SPREADSHEET + + + var V = a / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(PHI1),2)))); + var PHI2 = Math.atan2((Z + (e2 * V * (Math.sin(PHI1)))) , RootXYSqr); + + while (Math.abs(PHI1 - PHI2) > 0.000000001) { + PHI1 = PHI2; + V = a / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(PHI1),2)))); + PHI2 = Math.atan2((Z + (e2 * V * (Math.sin(PHI1)))) , RootXYSqr); + } + + return PHI2; + }, + + + XYZ_to_Long: function (X, Y) { + // Convert XYZ to Longitude (LAM) in Dec Degrees. + // Input: - _ + // X and Y cartesian coords in meters. + + var Pi = 3.14159265358979; + return Math.atan2(Y , X) * (180 / Pi); + }, + + Marc: function (bf0, n, PHI0, PHI) { + //Compute meridional arc. + //Input: - _ + // ellipsoid semi major axis multiplied by central meridian scale factor (bf0) in meters; _ + // n (computed from a, b and f0); _ + // lat of false origin (PHI0) and initial or final latitude of point (PHI) IN RADIANS. + + //this FUNCTION IS CALLED BY THE - _ + // "Lat_Long_to_North" and "InitialLat" FUNCTIONS + // this FUNCTION IS ALSO USED ON IT'S OWN IN THE "Projection and Transformation Calculations.xls" SPREADSHEET + + return bf0 * (((1 + n + ((5 / 4) * Math.pow(n,2)) + ((5 / 4) * Math.pow(n,3))) * (PHI - PHI0)) - (((3 * n) + (3 * Math.pow(n,2)) + ((21 / 8) * Math.pow(n,3))) * (Math.sin(PHI - PHI0)) * (Math.cos(PHI + PHI0))) + ((((15 / 8 + ) * Math.pow(n,2)) + ((15 / 8) * Math.pow(n,3))) * (Math.sin(2 * (PHI - PHI0))) * (Math.cos(2 * (PHI + PHI0)))) - (((35 / 24) * Math.pow(n,3)) * (Math.sin(3 * (PHI - PHI0))) * (Math.cos(3 * (PHI + PHI0))))); + }, + + Lat_Long_to_East: function (PHI, LAM, a, b, e0, f0, PHI0, LAM0) { + //Project Latitude and longitude to Transverse Mercator eastings. + //Input: - _ + // Latitude (PHI) and Longitude (LAM) in decimal degrees; _ + // ellipsoid axis dimensions (a & b) in meters; _ + // eastings of false origin (e0) in meters; _ + // central meridian scale factor (f0); _ + // latitude (PHI0) and longitude (LAM0) of false origin in decimal degrees. + + // Convert angle measures to radians + var Pi = 3.14159265358979; + var RadPHI = PHI * (Pi / 180); + var RadLAM = LAM * (Pi / 180); + var RadPHI0 = PHI0 * (Pi / 180); + var RadLAM0 = LAM0 * (Pi / 180); + + var af0 = a * f0; + var bf0 = b * f0; + var e2 = (Math.pow(af0,2) - Math.pow(bf0,2)) / Math.pow(af0,2); + var n = (af0 - bf0) / (af0 + bf0); + var nu = af0 / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(RadPHI),2) ))); + var rho = (nu * (1 - e2)) / (1 - (e2 * Math.pow(Math.sin(RadPHI),2) )); + var eta2 = (nu / rho) - 1; + var p = RadLAM - RadLAM0; + + var IV = nu * (Math.cos(RadPHI)); + var V = (nu / 6) * ( Math.pow(Math.cos(RadPHI),3)) * ((nu / rho) - (Math.pow(Math.tan(RadPHI),2))); + var VI = (nu / 120) * (Math.pow(Math.cos(RadPHI),5)) * (5 - (18 * (Math.pow(Math.tan(RadPHI),2))) + (Math.pow(Math.tan(RadPHI),4)) + (14 * eta2) - (58 * (Math.pow(Math.tan(RadPHI),2)) * eta2)); + + return e0 + (p * IV) + (Math.pow(p,3) * V) + (Math.pow(p,5) * VI); + }, + + Lat_Long_to_North: function (PHI, LAM, a, b, e0, n0, f0, PHI0, LAM0) { + // Project Latitude and longitude to Transverse Mercator northings + // Input: - _ + // Latitude (PHI) and Longitude (LAM) in decimal degrees; _ + // ellipsoid axis dimensions (a & b) in meters; _ + // eastings (e0) and northings (n0) of false origin in meters; _ + // central meridian scale factor (f0); _ + // latitude (PHI0) and longitude (LAM0) of false origin in decimal degrees. + + // REQUIRES THE "Marc" FUNCTION + + // Convert angle measures to radians + var Pi = 3.14159265358979; + var RadPHI = PHI * (Pi / 180); + var RadLAM = LAM * (Pi / 180); + var RadPHI0 = PHI0 * (Pi / 180); + var RadLAM0 = LAM0 * (Pi / 180); + + var af0 = a * f0; + var bf0 = b * f0; + var e2 = (Math.pow(af0,2) - Math.pow(bf0,2)) / Math.pow(af0,2); + var n = (af0 - bf0) / (af0 + bf0); + var nu = af0 / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(RadPHI),2)))); + var rho = (nu * (1 - e2)) / (1 - (e2 * Math.pow(Math.sin(RadPHI),2))); + var eta2 = (nu / rho) - 1; + var p = RadLAM - RadLAM0; + var M = OpenLayers.Projection.OS.Marc(bf0, n, RadPHI0, RadPHI); + + var I = M + n0; + var II = (nu / 2) * (Math.sin(RadPHI)) * (Math.cos(RadPHI)); + var III = ((nu / 24) * (Math.sin(RadPHI)) * (Math.pow(Math.cos(RadPHI),3))) * (5 - (Math.pow(Math.tan(RadPHI),2)) + (9 * eta2)); + var IIIA = ((nu / 720) * (Math.sin(RadPHI)) * (Math.pow(Math.cos(RadPHI),5))) * (61 - (58 * (Math.pow(Math.tan(RadPHI),2))) + (Math.pow(Math.tan(RadPHI),4))); + + return I + (Math.pow(p,2) * II) + (Math.pow(p,4) * III) + (Math.pow(p,6) * IIIA); + } + +}; + +/** + * Note: Two transforms declared + * Transforms from EPSG:4326 to EPSG:27700 and from EPSG:27700 to EPSG:4326 + * are set by this class. + */ +OpenLayers.Projection.addTransform("EPSG:4326", "EPSG:27700", + OpenLayers.Projection.OS.projectForwardBritish); +OpenLayers.Projection.addTransform("EPSG:27700", "EPSG:4326", + OpenLayers.Projection.OS.projectInverseBritish); +OpenLayers.Projection.addTransform("EPSG:900913", "EPSG:27700", + OpenLayers.Projection.OS.goog2osgb); +OpenLayers.Projection.addTransform("EPSG:27700", "EPSG:900913", + OpenLayers.Projection.OS.osgb2goog); diff --git a/web/js/map-OpenStreetMap.js b/web/js/map-OpenStreetMap.js new file mode 100644 index 000000000..04237e075 --- /dev/null +++ b/web/js/map-OpenStreetMap.js @@ -0,0 +1,150 @@ +YAHOO.util.Event.onContentReady('map', function() { + var map = new OpenLayers.Map("map", { + controls: [ + new OpenLayers.Control.ArgParser(), + //new OpenLayers.Control.LayerSwitcher(), + new OpenLayers.Control.Navigation(), + new OpenLayers.Control.PanZoom() + ], + displayProjection: new OpenLayers.Projection("EPSG:4326") + }); + var streetview = new fixmystreet.map_type("", { + zoomOffset: 14, + numZoomLevels: 4 + }); + map.addLayer(streetview); + + var centre = new OpenLayers.LonLat( fixmystreet.easting, fixmystreet.northing ); + centre.transform( + new OpenLayers.Projection("EPSG:27700"), + map.getProjectionObject() + ); + map.setCenter(centre, 2); +}); + + +// http://www.openstreetmap.org/openlayers/OpenStreetMap.js (added maxResolution) + +/** + * Namespace: Util.OSM + */ +OpenLayers.Util.OSM = {}; + +/** + * Constant: MISSING_TILE_URL + * {String} URL of image to display for missing tiles + */ +OpenLayers.Util.OSM.MISSING_TILE_URL = "http://www.openstreetmap.org/openlayers/img/404.png"; + +/** + * Property: originalOnImageLoadError + * {Function} Original onImageLoadError function. + */ +OpenLayers.Util.OSM.originalOnImageLoadError = OpenLayers.Util.onImageLoadError; + +/** + * Function: onImageLoadError + */ +OpenLayers.Util.onImageLoadError = function() { + if (this.src.match(/^http:\/\/[abc]\.[a-z]+\.openstreetmap\.org\//)) { + this.src = OpenLayers.Util.OSM.MISSING_TILE_URL; + } else if (this.src.match(/^http:\/\/[def]\.tah\.openstreetmap\.org\//)) { + // do nothing - this layer is transparent + } else { + OpenLayers.Util.OSM.originalOnImageLoadError; + } +}; + +/** + * Class: OpenLayers.Layer.OSM.Mapnik + * + * Inherits from: + * - <OpenLayers.Layer.OSM> + */ +OpenLayers.Layer.OSM.Mapnik = OpenLayers.Class(OpenLayers.Layer.OSM, { + /** + * Constructor: OpenLayers.Layer.OSM.Mapnik + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + var url = [ + "http://a.tile.openstreetmap.org/${z}/${x}/${y}.png", + "http://b.tile.openstreetmap.org/${z}/${x}/${y}.png", + "http://c.tile.openstreetmap.org/${z}/${x}/${y}.png" + ]; + options = OpenLayers.Util.extend({ + /* Below line added to OSM's file in order to allow minimum zoom level */ + maxResolution: 156543.0339/Math.pow(2, options.zoomOffset || 0), + numZoomLevels: 19, + buffer: 0 + }, options); + var newArguments = [name, url, options]; + OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments); + }, + + CLASS_NAME: "OpenLayers.Layer.OSM.Mapnik" +}); + +/** + * Class: OpenLayers.Layer.OSM.Osmarender + * + * Inherits from: + * - <OpenLayers.Layer.OSM> + */ +OpenLayers.Layer.OSM.Osmarender = OpenLayers.Class(OpenLayers.Layer.OSM, { + /** + * Constructor: OpenLayers.Layer.OSM.Osmarender + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + var url = [ + "http://a.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png", + "http://b.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png", + "http://c.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png" + ]; + options = OpenLayers.Util.extend({ numZoomLevels: 18, buffer: 0 }, options); + var newArguments = [name, url, options]; + OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments); + }, + + CLASS_NAME: "OpenLayers.Layer.OSM.Osmarender" +}); + +/** + * Class: OpenLayers.Layer.OSM.CycleMap + * + * Inherits from: + * - <OpenLayers.Layer.OSM> + */ +OpenLayers.Layer.OSM.CycleMap = OpenLayers.Class(OpenLayers.Layer.OSM, { + /** + * Constructor: OpenLayers.Layer.OSM.CycleMap + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + var url = [ + "http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png" + ]; + options = OpenLayers.Util.extend({ + /* Below line added to OSM's file in order to allow minimum zoom level */ + maxResolution: 156543.0339/Math.pow(2, options.zoomOffset || 0), + numZoomLevels: 19, + buffer: 0 + }, options); + var newArguments = [name, url, options]; + OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments); + }, + + CLASS_NAME: "OpenLayers.Layer.OSM.CycleMap" +}); diff --git a/web/js/map-bing-ol.js b/web/js/map-bing-ol.js new file mode 100644 index 000000000..ba9445326 --- /dev/null +++ b/web/js/map-bing-ol.js @@ -0,0 +1,96 @@ +YAHOO.util.Event.onContentReady('map', function() { + var map = new OpenLayers.Map("map", { + controls: [ + new OpenLayers.Control.ArgParser(), + //new OpenLayers.Control.LayerSwitcher(), + new OpenLayers.Control.Navigation(), + new OpenLayers.Control.PanZoom() + ], + displayProjection: new OpenLayers.Projection("EPSG:4326") + }); + var bing = new OpenLayers.Layer.Bing("", { + zoomOffset: 13, + numZoomLevels: 4 + }); + map.addLayer(bing); + + var centre = new OpenLayers.LonLat( fixmystreet.easting, fixmystreet.northing ); + centre.transform( + new OpenLayers.Projection("EPSG:27700"), + map.getProjectionObject() + ); + map.setCenter(centre, 3); +}); + +OpenLayers.Util.OS = {}; +OpenLayers.Util.OS.MISSING_TILE_URL = "http://openstreetmap.org/openlayers/img/404.png"; +OpenLayers.Util.OS.originalOnImageLoadError = OpenLayers.Util.onImageLoadError; +OpenLayers.Util.onImageLoadError = function() { + OpenLayers.Util.OS.originalOnImageLoadError; +}; + +OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, { + initialize: function(name, options) { + var url = []; + options = OpenLayers.Util.extend({ + /* Below line added to OSM's file in order to allow minimum zoom level */ + maxResolution: 156543.0339/Math.pow(2, options.zoomOffset || 0), + numZoomLevels: 18, + transitionEffect: "resize", + sphericalMercator: true, + attribution: "© Microsoft / OS 2010" + }, options); + var newArguments = [name, url, options]; + OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArguments); + }, + + get_quadkey: function(x, y, level) { + var key = ''; + for (var i = level; i > 0; i--) { + var digit = 0; + var mask = 1 << (i - 1); + if ((x & mask) != 0) { + digit++; + } + if ((y & mask) != 0) { + digit += 2; + } + key += digit; + } + return key; + }, + + getURL: function (bounds) { + var res = this.map.getResolution(); + var x = Math.round((bounds.left - this.maxExtent.left) + / (res * this.tileSize.w)); + var y = Math.round((this.maxExtent.top - bounds.top) + / (res * this.tileSize.h)); + var z = this.serverResolutions != null ? + OpenLayers.Util.indexOf(this.serverResolutions, res) : + this.map.getZoom() + this.zoomOffset; + + if (z == 16) { + var url = [ + "http://a.os.openstreetmap.org/sv/${z}/${x}/${y}.png", + "http://b.os.openstreetmap.org/sv/${z}/${x}/${y}.png", + "http://c.os.openstreetmap.org/sv/${z}/${x}/${y}.png" + ]; + } else { + var url = [ + "http://ecn.t0.tiles.virtualearth.net/tiles/r${id}.png?g=587&productSet=mmOS", + "http://ecn.t1.tiles.virtualearth.net/tiles/r${id}.png?g=587&productSet=mmOS", + "http://ecn.t2.tiles.virtualearth.net/tiles/r${id}.png?g=587&productSet=mmOS", + "http://ecn.t3.tiles.virtualearth.net/tiles/r${id}.png?g=587&productSet=mmOS" + ]; + } + var s = '' + x + y + z; + url = this.selectUrl(s, url); + + var id = this.get_quadkey(x, y, z); + var path = OpenLayers.String.format(url, {'id': id, 'x': x, 'y': y, 'z': z}); + return path; + }, + + CLASS_NAME: "OpenLayers.Layer.Bing" +}); diff --git a/web/js/map-bing.js b/web/js/map-bing.js new file mode 100644 index 000000000..fbbbcc676 --- /dev/null +++ b/web/js/map-bing.js @@ -0,0 +1,21 @@ +YAHOO.util.Event.onContentReady('map', function() { + var centre = new Microsoft.Maps.Location( fixmystreet.lat, fixmystreet.lon ); + var map = new Microsoft.Maps.Map(document.getElementById("map"), { + mapTypeId: Microsoft.Maps.MapTypeId.ordnanceSurvey, + center: centre, + zoom: 15, + enableClickableLogo: false, + enableSearchLogo: false, + showCopyright: false, + showDashboard: true, + showLogo: false, + showScalebar: false + }); + //minZoomLevel: 14, + //numZoomLevels: 4 + + Microsoft.Maps.Events.addHandler(map, "viewchangestart", function(e) { + /* Doesn't work */ + if (map.getTargetZoom() < 12) return false; + }); +}); diff --git a/web/js/map-google.js b/web/js/map-google.js new file mode 100644 index 000000000..77e54ab7c --- /dev/null +++ b/web/js/map-google.js @@ -0,0 +1,22 @@ +YAHOO.util.Event.onContentReady('map', function() { + var centre = new google.maps.LatLng( fixmystreet.lat, fixmystreet.lon ); + var map = new google.maps.Map(document.getElementById("map"), { + mapTypeId: google.maps.MapTypeId.ROADMAP, + center: centre, + zoom: 16, + disableDefaultUI: true, + navigationControl: true, + navigationControlOptions: { + style: google.maps.NavigationControlStyle.SMALL + }, + mapTypeControl: true, + mapTypeControlOptions: { + style: google.maps.MapTypeControlStyle.DROPDOWN_MENU + } + }); + + google.maps.event.addListener(map, "zoom_changed", function() { + if (map.getZoom() < 13) map.setZoom(13); + if (map.getZoom() > 17) map.setZoom(17); + }); +}); diff --git a/web/js/map-streetview.js b/web/js/map-streetview.js new file mode 100644 index 000000000..088e5b6a2 --- /dev/null +++ b/web/js/map-streetview.js @@ -0,0 +1,88 @@ +YAHOO.util.Event.onContentReady('map', function() { + var map = new OpenLayers.Map("map", { + controls: [ + new OpenLayers.Control.ArgParser(), + //new OpenLayers.Control.LayerSwitcher(), + new OpenLayers.Control.Navigation(), + new OpenLayers.Control.PanZoom() + ], + displayProjection: new OpenLayers.Projection("EPSG:4326") + }); + var streetview = new OpenLayers.Layer.StreetView("OS StreetView (1:10000)", { + zoomOffset: 14, + numZoomLevels: 4 + }); + map.addLayer(streetview); + + var centre = new OpenLayers.LonLat( fixmystreet.easting, fixmystreet.northing ); + centre.transform( + new OpenLayers.Projection("EPSG:27700"), + map.getProjectionObject() + ); + map.setCenter(centre, 2); +}); + + +// http://os.openstreetmap.org/openlayers/OS.js (added one line) + +/** + * Namespace: Util.OS + */ +OpenLayers.Util.OS = {}; + +/** + * Constant: MISSING_TILE_URL + * {String} URL of image to display for missing tiles + */ +OpenLayers.Util.OS.MISSING_TILE_URL = "http://openstreetmap.org/openlayers/img/404.png"; + +/** + * Property: originalOnImageLoadError + * {Function} Original onImageLoadError function. + */ +OpenLayers.Util.OS.originalOnImageLoadError = OpenLayers.Util.onImageLoadError; + +/** + * Function: onImageLoadError + */ +OpenLayers.Util.onImageLoadError = function() { + OpenLayers.Util.OS.originalOnImageLoadError; +}; + +/** + * @requires OpenLayers/Layer/XYZ.js + * + * Class: OpenLayers.Layer.StreetView + * + * Inherits from: + * - <OpenLayers.Layer.XYZ> + */ +OpenLayers.Layer.StreetView = OpenLayers.Class(OpenLayers.Layer.XYZ, { + /** + * Constructor: OpenLayers.Layer.StreetView + * + * Parameters: + * name - {String} + * url - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + var url = [ + "http://a.os.openstreetmap.org/sv/${z}/${x}/${y}.png", + "http://b.os.openstreetmap.org/sv/${z}/${x}/${y}.png", + "http://c.os.openstreetmap.org/sv/${z}/${x}/${y}.png" + ]; + options = OpenLayers.Util.extend({ + /* Below line added to OSM's file in order to allow minimum zoom level */ + maxResolution: 156543.0339/Math.pow(2, options.zoomOffset || 0), + numZoomLevels: 18, + transitionEffect: "resize", + sphericalMercator: true, + attribution: "Contains Ordnance Survey data © Crown copyright and database right 2010" + }, options); + var newArguments = [name, url, options]; + OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArguments); + }, + + CLASS_NAME: "OpenLayers.Layer.StreetView" +}); diff --git a/web/js/map-tilma-ol.js b/web/js/map-tilma-ol.js new file mode 100644 index 000000000..cfd196c1d --- /dev/null +++ b/web/js/map-tilma-ol.js @@ -0,0 +1,42 @@ +YAHOO.util.Event.onContentReady('map', function() { + var map = new OpenLayers.Map("map", { + controls: [ + new OpenLayers.Control.ArgParser(), + new OpenLayers.Control.Navigation(), + new OpenLayers.Control.PanPanel() + ] + }); + var tilma = new OpenLayers.Layer.Tilma("Tilma", { + maxResolution: fixmystreet.maxResolution, + tileSize: new OpenLayers.Size(fixmystreet.tilewidth, fixmystreet.tileheight), + map_type: fixmystreet.tile_type + }); + map.addLayer(tilma); + + var centre = new OpenLayers.LonLat( fixmystreet.easting, fixmystreet.northing ); + map.setCenter(centre); +}); + +OpenLayers.Layer.Tilma = OpenLayers.Class(OpenLayers.Layer.XYZ, { + initialize: function(name, options) { + var url = "http://tilma.mysociety.org/tileserver/${type}/${x},${y}/png"; + options = OpenLayers.Util.extend({ + transitionEffect: "resize", + numZoomLevels: 1, + units: "m", + maxExtent: new OpenLayers.Bounds(0, 0, 700000, 1300000), + }, options); + var newArguments = [name, url, options]; + OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArguments); + }, + + getURL: function (bounds) { + var res = this.map.getResolution(); + var x = Math.round(bounds.left / (res * this.tileSize.w)); + var y = Math.round(bounds.bottom / (res * this.tileSize.h)); + var path = OpenLayers.String.format(this.url, {'x': x, 'y': y, 'type': this.map_type}); + return path; + }, + + CLASS_NAME: "OpenLayers.Layer.Tilma" +}); diff --git a/web/js/map-tilma.js b/web/js/map-tilma.js new file mode 100644 index 000000000..1ae38cff1 --- /dev/null +++ b/web/js/map-tilma.js @@ -0,0 +1,320 @@ +/* + * map-tilma.js + * JavaScript specifically for the tilma based maps + */ + +function compass_pan(e, a) { + YAHOO.util.Event.preventDefault(e); + if (a.home) { + a.x = a.orig_x-drag_x; + a.y = a.orig_y-drag_y; + } + pan(a.x, a.y); +} + +YAHOO.util.Event.onContentReady('compass', function() { + var ua=navigator.userAgent.toLowerCase(); + // if (document.getElementById('mapForm') && (/safari/.test(ua) || /Konqueror/.test(ua))) return; + if (document.getElementById('map').offsetWidth > 510) return; + + var points = this.getElementsByTagName('a'); + YAHOO.util.Event.addListener(points[1], 'click', compass_pan, { x:0, y:fixmystreet.tileheight }); + YAHOO.util.Event.addListener(points[3], 'click', compass_pan, { x:fixmystreet.tilewidth, y:0 }); + YAHOO.util.Event.addListener(points[5], 'click', compass_pan, { x:-fixmystreet.tilewidth, y:0 }); + YAHOO.util.Event.addListener(points[7], 'click', compass_pan, { x:0, y:-fixmystreet.tileheight }); + YAHOO.util.Event.addListener(points[0], 'click', compass_pan, { x:fixmystreet.tilewidth, y:fixmystreet.tileheight }); + YAHOO.util.Event.addListener(points[2], 'click', compass_pan, { x:-fixmystreet.tilewidth, y:fixmystreet.tileheight }); + YAHOO.util.Event.addListener(points[6], 'click', compass_pan, { x:fixmystreet.tilewidth, y:-fixmystreet.tileheight }); + YAHOO.util.Event.addListener(points[8], 'click', compass_pan, { x:-fixmystreet.tilewidth, y:-fixmystreet.tileheight }); + YAHOO.util.Event.addListener(points[4], 'click', compass_pan, { home:1, orig_x:drag_x, orig_y:drag_y }); +}); + +YAHOO.util.Event.onContentReady('map', function() { + var ua=navigator.userAgent.toLowerCase(); + // if (document.getElementById('mapForm') && (/safari/.test(ua) || /Konqueror/.test(ua))) return; + if (document.getElementById('map').offsetWidth > 510) return; + new YAHOO.util.DDMap('map'); + update_tiles(fixmystreet.start_x, fixmystreet.start_y, true); +}); + + +YAHOO.util.Event.addListener('hide_pins_link', 'click', function(e) { + YAHOO.util.Event.preventDefault(e); + if (this.innerHTML == 'Show pins') { + YAHOO.util.Dom.setStyle('pins', 'display', 'block'); + this.innerHTML = 'Hide pins'; + } else if (this.innerHTML == 'Dangos pinnau') { + YAHOO.util.Dom.setStyle('pins', 'display', 'block'); + this.innerHTML = 'Cuddio pinnau'; + } else if (this.innerHTML == 'Cuddio pinnau') { + YAHOO.util.Dom.setStyle('pins', 'display', 'none'); + this.innerHTML = 'Dangos pinnau'; + } else if (this.innerHTML == 'Hide pins') { + YAHOO.util.Dom.setStyle('pins', 'display', 'none'); + this.innerHTML = 'Show pins'; + } +}); +YAHOO.util.Event.addListener('all_pins_link', 'click', function(e) { + YAHOO.util.Event.preventDefault(e); + YAHOO.util.Dom.setStyle('pins', 'display', 'block'); + var welsh = 0; + if (this.innerHTML == 'Include stale reports') { + this.innerHTML = 'Hide stale reports'; + document.getElementById('all_pins').value = '1'; + load_pins(fixmystreet.x, fixmystreet.y); + } else if (this.innerHTML == 'Cynnwys hen adroddiadau') { + this.innerHTML = 'Cuddio hen adroddiadau'; + document.getElementById('all_pins').value = '1'; + welsh = 1; + load_pins(fixmystreet.x, fixmystreet.y); + } else if (this.innerHTML == 'Cuddio hen adroddiadau') { + this.innerHTML = 'Cynnwys hen adroddiadau'; + welsh = 1; + document.getElementById('all_pins').value = ''; + load_pins(fixmystreet.x, fixmystreet.y); + } else if (this.innerHTML == 'Hide stale reports') { + this.innerHTML = 'Include stale reports'; + document.getElementById('all_pins').value = ''; + load_pins(fixmystreet.x, fixmystreet.y); + } + if (welsh) { + document.getElementById('hide_pins_link').innerHTML = 'Cuddio pinnau'; + } else { + document.getElementById('hide_pins_link').innerHTML = 'Hide pins'; + } +}); + +// I love the global +var tile_x = 0; +var tile_y = 0; + +var myAnim; +function pan(x, y) { + if (!myAnim || !myAnim.isAnimated()) { + myAnim = new YAHOO.util.Motion('drag', { points:{by:[x,y]} }, 10, YAHOO.util.Easing.easeOut); + myAnim.useSeconds = false; + //myAnim.onTween.subscribe(function(){ update_tiles(x/10, y/10, false); }); + myAnim.onComplete.subscribe(function(){ + update_tiles(x, y, false); + cleanCache(); + }); + myAnim.animate(); + } +} + +var drag_x = 0; +var drag_y = 0; +function update_tiles(dx, dy, force) { + dx = getInt(dx); dy = getInt(dy); + if (!dx && !dy && !force) return; + var old_drag_x = drag_x; + var old_drag_y = drag_y; + drag_x += dx; + drag_y += dy; + + var drag = document.getElementById('drag'); + drag.style.left = drag_x + 'px'; + drag.style.top = drag_y + 'px'; + + var horizontal = Math.floor(old_drag_x/fixmystreet.tilewidth) - Math.floor(drag_x/fixmystreet.tilewidth); + var vertical = Math.floor(old_drag_y/fixmystreet.tileheight) - Math.floor(drag_y/fixmystreet.tileheight); + if (!horizontal && !vertical && !force) return; + fixmystreet.x += horizontal; + + tile_x += horizontal; + fixmystreet.y -= vertical; + tile_y += vertical; + var url = [ root_path + '/tilma/tileserver/' + fixmystreet.tile_type + '/', fixmystreet.x, '-', (fixmystreet.x+5), ',', fixmystreet.y, '-', (fixmystreet.y+5), '/JSON' ].join(''); + YAHOO.util.Connect.asyncRequest('GET', url, { + success: urls_loaded, failure: urls_not_loaded, + argument: [tile_x, tile_y] + }); + + if (force) return; + load_pins(fixmystreet.x, fixmystreet.y); +} + +function load_pins(x, y) { + if (document.getElementById('formX')) { + all_pins = ''; + if (document.getElementById('all_pins')) { + all_pins = document.getElementById('all_pins').value; + } + var ajax_params = [ 'sx=' + document.getElementById('formX').value, + 'sy=' + document.getElementById('formY').value, + 'x=' + (x+3), + 'y=' + (y+3), + 'all_pins=' + all_pins ]; + + if (document.getElementById('extra_param')) { + ajax_params.push(document.getElementById('extra_param').name + '=' + document.getElementById('extra_param').value); + } + + var url = [ root_path , '/ajax?', ajax_params.join(';')].join(''); + YAHOO.util.Connect.asyncRequest('GET', url, { + success: pins_loaded + }); + } +} + +function pins_loaded(o) { + var data = eval(o.responseText); + document.getElementById('pins').innerHTML = data.pins; + if (typeof(data.current) != 'undefined') + document.getElementById('current').innerHTML = data.current; + if (typeof(data.current_near) != 'undefined') + document.getElementById('current_near').innerHTML = data.current_near; + if (typeof(data.fixed_near) != 'undefined') + document.getElementById('fixed_near').innerHTML = data.fixed_near; +} + +function urls_not_loaded(o) { /* Nothing yet */ } + +// Load 6x6 grid of tiles around current 2x2 +function urls_loaded(o) { + var tiles = eval(o.responseText); + var drag = document.getElementById('drag'); + for (var i=0; i<6; i++) { + var ii = (i + o.argument[1]); + for (var j=0; j<6; j++) { + if (tiles[i][j] == null) continue; + var jj = (j + o.argument[0]); + var id = [ 't', ii, '.', jj ].join(''); + var xx = fixmystreet.x+j; + var yy = fixmystreet.y+5-i; + var img = document.getElementById(id); + if (img) { + if (!img.galleryimg) { img.galleryimg = false; } + img.onclick = drag_check; + tileCache[id] = { x: xx, y: yy, t: img }; + continue; + } + img = cloneNode(); + img.style.top = ((ii-2)*fixmystreet.tileheight) + 'px'; + img.style.left = ((jj-2)*fixmystreet.tilewidth) + 'px'; + img.name = [ 'tile_', xx, '.', yy ].join('') + img.id = id; + if (browser) { + img.style.visibility = 'hidden'; + img.onload=function() { this.style.visibility = 'visible'; } + } + img.src = 'http://tilma.mysociety.org/tileserver/' + fixmystreet.tile_type + '/' + tiles[i][j]; + tileCache[id] = { x: xx, y: yy, t: img }; + drag.appendChild(img); + } + } +} + +var imgElCache; +function cloneNode() { + var img = null; + if (!imgElCache) { + var form = document.getElementById('mapForm'); + if (form) { + img = imgElCache = document.createElement('input'); + img.type = 'image'; + } else { + img = imgElCache = document.createElement('img'); + } + img.onclick = drag_check; + img.style.position = 'absolute'; + img.style.width = fixmystreet.tilewidth + 'px'; + img.style.height = fixmystreet.tileheight + 'px'; + img.galleryimg = false; + img.alt = 'Loading...'; + } else { + img = imgElCache.cloneNode(true); + } + return img; +} + +var tileCache=[]; +function cleanCache() { + for (var i in tileCache) { + if (tileCache[i].x < fixmystreet.x || tileCache[i].x > fixmystreet.x+5 || tileCache[i].y < fixmystreet.y || tileCache[i].y > fixmystreet.y+5) { + var t = tileCache[i].t; + t.parentNode.removeChild(t); // de-leak? + delete tileCache[i]; + } + } +} + +/* Called every mousemove, so on first call, overwrite itself with quicker version */ +function get_posn(ev) { + var posx, posy; + if (ev.pageX || ev.pageY) { + get_posn = function(e) { + return { x: e.pageX, y: e.pageY }; + }; + } else if (ev.clientX || ev.clientY) { + get_posn = function(e) { + return { + x: e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft, + y: e.clientY + document.body.scrollTop + document.documentElement.scrollTop + }; + }; + } else { + get_posn = function(e) { + return { x: undef, y: undef }; + }; + } + return get_posn(ev); +} + +function setCursor(s) { + var drag = document.getElementById('drag'); + var inputs = drag.getElementsByTagName('input'); + for (var i=0; i<inputs.length; i++) { + inputs[i].style.cursor = s; + } +} + +var in_drag = false; +function drag_check(e) { + if (in_drag) { + in_drag = false; + return false; + } + return true; +} + +/* Simpler version of DDProxy */ +var mouse_pos = {}; +YAHOO.util.DDMap = function(id, sGroup, config) { + this.init(id, sGroup, config); +}; +YAHOO.extend(YAHOO.util.DDMap, YAHOO.util.DD, { + scroll: false, + b4MouseDown: function(e) { }, + startDrag: function(x, y) { + mouse_pos = { x: x, y: y }; + setCursor('move'); + in_drag = true; + }, + b4Drag: function(e) { }, + onDrag: function(e) { + var point = get_posn(e); + if (point == mouse_pos) return false; + var dx = point.x-mouse_pos.x; + var dy = point.y-mouse_pos.y; + mouse_pos = point; + update_tiles(dx, dy, false); + }, + endDrag: function(e) { + setCursor('crosshair'); + cleanCache(); + }, + toString: function() { + return ("DDMap " + this.id); + } +}); + +var browser = 1; +var ua=navigator.userAgent.toLowerCase(); +if (!/opera|safari|gecko/.test(ua) && typeof document.all!='undefined') + browser=0; + +function getInt(n) { + n = parseInt(n); return (isNaN(n) ? 0 : n); +} + diff --git a/web/questionnaire.cgi b/web/questionnaire.cgi index 1da410b80..33e823560 100755 --- a/web/questionnaire.cgi +++ b/web/questionnaire.cgi @@ -10,6 +10,7 @@ use strict; use Standard; +use Utils; use Error qw(:try); use CrossSell; use mySociety::AuthToken; @@ -213,17 +214,19 @@ sub display_questionnaire { return $error; } my $reported_date_time = Page::prettify_epoch($q, $problem->{time}); - my ($x, $y, $x_tile, $y_tile, $px, $py) = Page::os_to_px_with_adjust($q, $problem->{easting}, $problem->{northing}, undef, undef); - - my $pins = Page::display_pin($q, $px, $py, $problem->{state} eq 'fixed'?'green':'red'); my $problem_text = Page::display_problem_text($q, $problem); my $updates = Page::display_problem_updates($problem->{id}, $q); my %vars = ( input_h => \%input_h, - map_start => Page::display_map($q, x => $x_tile, y => $y_tile, pins => $pins, - px => $px, py => $py, pre => $problem_text, post => $updates ), - map_end => Page::display_map_end(0), + map_start => FixMyStreet::Map::display_map($q, + easting => $problem->{easting}, northing => $problem->{northing}, + pins => [ + [ $problem->{easting}, $problem->{northing}, $problem->{state} eq 'fixed'?'green':'red' ], + ], + pre => $problem_text, post => $updates + ), + map_end => FixMyStreet::Map::display_map_end(0), heading => _('Questionnaire'), yes => _('Yes'), no => _('No'), diff --git a/web/reports.cgi b/web/reports.cgi index 4faee63f9..fe4213be4 100755 --- a/web/reports.cgi +++ b/web/reports.cgi @@ -12,7 +12,7 @@ use strict; use Standard; use URI::Escape; -use mySociety::Alert; +use FixMyStreet::Alert; use mySociety::MaPit; use mySociety::Web qw(ent NewURL); use mySociety::VotingArea; @@ -117,7 +117,7 @@ sub main { } print $q->header( -type => 'application/xml; charset=utf-8' ); my $xsl = Cobrand::feed_xsl($cobrand); - my $out = mySociety::Alert::generate_rss($type, $xsl, "/$url", \@params, \%title_params, $cobrand, $q); + my $out = FixMyStreet::Alert::generate_rss($type, $xsl, "/$url", \@params, \%title_params, $cobrand, $q); $out =~ s/matthew.fixmystreet/emptyhomes.matthew.fixmystreet/g if $q->{site} eq 'emptyhomes'; print $out; return; diff --git a/web/robots.txt b/web/robots.txt index e69de29bb..53675186c 100644 --- a/web/robots.txt +++ b/web/robots.txt @@ -0,0 +1,5 @@ +User-agent: msnbot +Crawl-delay: 3 + +User-agent: bingbot +Crawl-delay: 3 diff --git a/web/rss.cgi b/web/rss.cgi index a2d9bcb4f..a716d93e9 100755 --- a/web/rss.cgi +++ b/web/rss.cgi @@ -12,7 +12,8 @@ use strict; use Error qw(:try); use Standard; use URI::Escape; -use mySociety::Alert; +use FixMyStreet::Alert; +use FixMyStreet::Geocode; use mySociety::MaPit; use mySociety::GeoUtil; use mySociety::Gaze; @@ -34,20 +35,20 @@ sub main { return; } my $qs = 'report/' . $id; - $out = mySociety::Alert::generate_rss($type, $xsl, $qs, [$id], undef, $cobrand, $q); + $out = FixMyStreet::Alert::generate_rss($type, $xsl, $qs, [$id], undef, $cobrand, $q); } elsif ($type eq 'new_problems' || $type eq 'new_fixed_problems') { - $out = mySociety::Alert::generate_rss($type, $xsl, '', undef, undef, $cobrand, $q); + $out = FixMyStreet::Alert::generate_rss($type, $xsl, '', undef, undef, $cobrand, $q); } elsif ($type eq 'council_problems') { my $id = $q->param('id'); my $qs = '/'.$id; - $out = mySociety::Alert::generate_rss($type, $xsl, $qs, [$id], undef, $cobrand. $q); + $out = FixMyStreet::Alert::generate_rss($type, $xsl, $qs, [$id], undef, $cobrand. $q); } elsif ($type eq 'area_problems') { my $id = $q->param('id'); my $va_info = mySociety::MaPit::call('area', $id); my $qs = '/'.$id; - $out = mySociety::Alert::generate_rss($type, $xsl, $qs, [$id], { NAME => $va_info->{name} }, $cobrand, $q); + $out = FixMyStreet::Alert::generate_rss($type, $xsl, $qs, [$id], { NAME => $va_info->{name} }, $cobrand, $q); } elsif ($type eq 'all_problems') { - $out = mySociety::Alert::generate_rss($type, $xsl, '', undef, undef, $cobrand, $q); + $out = FixMyStreet::Alert::generate_rss($type, $xsl, '', undef, undef, $cobrand, $q); } else { my $base = mySociety::Config::get('BASE_URL'); print $q->redirect($base . '/alert'); @@ -71,6 +72,13 @@ sub rss_local_problems { $d = '' unless $d =~ /^\d+$/; my $d_str = ''; $d_str = "/$d" if $d; + my $state = $q->param('state') || 'all'; + $state = 'all' unless $state =~ /^(all|open|fixed)$/; + + # state is getting lost in the redirects. Add it on to the end as a query + my $state_qs = "?state=$state" unless $state eq 'all'; + + $state = 'confirmed' if $state eq 'open'; my $cobrand = Page::get_cobrand($q); my $base = Cobrand::base_url($cobrand); @@ -78,33 +86,31 @@ sub rss_local_problems { ($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"); + print $q->redirect(-location => "$base/rss/n/$e,$n$d_str$state_qs"); return ''; } elsif ($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"); + print $q->redirect(-location => "$base/rss/n/$e,$n$d_str$state_qs"); return ''; } elsif ($e && $n) { - $x = int(Page::os_to_tile($e)); - $y = int(Page::os_to_tile($n)); ($lat, $lon) = mySociety::GeoUtil::national_grid_to_wgs84($e, $n, 'G'); } elsif ($pc) { my $error; try { - ($x, $y, $e, $n, $error) = Page::geocode($pc, $q); + ($e, $n, $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"); + print $q->redirect(-location => "$base/rss/n/$e,$n$d_str$state_qs"); } return ''; } else { die "Missing E/N, x/y, lat/lon, or postcode parameter in RSS feed"; } - my $qs = "?x=$x;y=$y"; + my $qs = '?e=' . int($e) . ';n=' . int($n); if ($d) { $qs .= ";d=$d"; $d = 100 if $d > 100; @@ -114,6 +120,10 @@ sub rss_local_problems { } my $xsl = Cobrand::feed_xsl($cobrand); - return mySociety::Alert::generate_rss('local_problems', $xsl, $qs, [$e, $n, $d], undef, $cobrand, $q); + if ($state eq 'all') { + return FixMyStreet::Alert::generate_rss('local_problems', $xsl, $qs, [$e, $n, $d], undef, $cobrand, $q); + } else { + return FixMyStreet::Alert::generate_rss('local_problems_state', $xsl, $qs, [$e, $n, $d, $state], undef, $cobrand, $q); + } } diff --git a/web/tms-signup.cgi b/web/tms-signup.cgi index 2bafd1543..44099417f 100755 --- a/web/tms-signup.cgi +++ b/web/tms-signup.cgi @@ -12,7 +12,6 @@ use strict; use Standard; use Digest::SHA1 qw(sha1_hex); use CrossSell; -use mySociety::Alert; use mySociety::AuthToken; use mySociety::Config; use mySociety::EmailUtil qw(is_valid_email); |