diff options
Diffstat (limited to 'perllib/FixMyStreet')
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Location.pm | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Report/New.pm | 8 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Bromley.pm | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Default.pm | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Zurich.pm | 16 | ||||
-rw-r--r-- | perllib/FixMyStreet/Geocode.pm | 24 | ||||
-rw-r--r-- | perllib/FixMyStreet/Geocode/Bing.pm | 9 | ||||
-rw-r--r-- | perllib/FixMyStreet/Geocode/Google.pm | 7 | ||||
-rw-r--r-- | perllib/FixMyStreet/Geocode/OSM.pm | 7 | ||||
-rw-r--r-- | perllib/FixMyStreet/Geocode/Zurich.pm | 107 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map.pm | 21 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/OSM.pm | 23 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/Zurich.pm | 177 |
13 files changed, 367 insertions, 36 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Location.pm b/perllib/FixMyStreet/App/Controller/Location.pm index e8bf2cd1c..fd3fadd9f 100644 --- a/perllib/FixMyStreet/App/Controller/Location.pm +++ b/perllib/FixMyStreet/App/Controller/Location.pm @@ -5,6 +5,7 @@ use namespace::autoclean; BEGIN {extends 'Catalyst::Controller'; } use Encode; +use FixMyStreet::Geocode; =head1 NAME diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm index dedd447ee..7759bb938 100644 --- a/perllib/FixMyStreet/App/Controller/Report/New.pm +++ b/perllib/FixMyStreet/App/Controller/Report/New.pm @@ -4,7 +4,6 @@ use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller'; } -use FixMyStreet::Geocode; use Encode; use List::MoreUtils qw(uniq); use POSIX 'strcoll'; @@ -928,6 +927,13 @@ sub check_for_errors : Private { %{ $c->stash->{report}->check_for_errors }, ); + # Zurich, we don't care about title or name + # There is no title, and name is optional + if ( $c->cobrand->moniker eq 'zurich' ) { + delete $field_errors{title}; + delete $field_errors{name}; + } + # FIXME: need to check for required bromley fields here # if they're got the login details wrong when signing in then diff --git a/perllib/FixMyStreet/Cobrand/Bromley.pm b/perllib/FixMyStreet/Cobrand/Bromley.pm index c33135673..a4c4f4ffc 100644 --- a/perllib/FixMyStreet/Cobrand/Bromley.pm +++ b/perllib/FixMyStreet/Cobrand/Bromley.pm @@ -29,7 +29,7 @@ sub disambiguate_location { my $town = 'Bromley'; # Bing turns High St Bromley into Bromley High St which is in # Bromley by Bow. - if ( $string =~ /high\+st/i ) { + if ( $string =~ /high\s+st/i ) { $town .= ', BR1'; } return { diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm index cc8f6c526..86ea693b7 100644 --- a/perllib/FixMyStreet/Cobrand/Default.pm +++ b/perllib/FixMyStreet/Cobrand/Default.pm @@ -4,6 +4,7 @@ use base 'FixMyStreet::Cobrand::Base'; use strict; use warnings; use FixMyStreet; +use FixMyStreet::Geocode::Bing; use Encode; use URI; use Digest::MD5 qw(md5_hex); diff --git a/perllib/FixMyStreet/Cobrand/Zurich.pm b/perllib/FixMyStreet/Cobrand/Zurich.pm new file mode 100644 index 000000000..e5d646c8b --- /dev/null +++ b/perllib/FixMyStreet/Cobrand/Zurich.pm @@ -0,0 +1,16 @@ +package FixMyStreet::Cobrand::Zurich; +use base 'FixMyStreet::Cobrand::Default'; + +use strict; +use warnings; + +sub enter_postcode_text { + my ( $self ) = @_; + return _('Enter a Zürich street name'); +} + +sub example_places { + return [ 'Langstrasse', 'Basteiplatz' ]; +} + +1; diff --git a/perllib/FixMyStreet/Geocode.pm b/perllib/FixMyStreet/Geocode.pm index f92e9cc9a..6cfd960ed 100644 --- a/perllib/FixMyStreet/Geocode.pm +++ b/perllib/FixMyStreet/Geocode.pm @@ -13,6 +13,7 @@ use URI::Escape; use FixMyStreet::Geocode::Bing; use FixMyStreet::Geocode::Google; use FixMyStreet::Geocode::OSM; +use FixMyStreet::Geocode::Zurich; # lookup STRING CONTEXT # Given a user-inputted string, try and convert it into co-ordinates using either @@ -33,18 +34,27 @@ sub lookup { # Canonicalises, and then passes to some external API to look stuff up. sub string { my ($s, $c) = @_; + + my $service = $c->config->{GEOCODER}; + $service = $service->{type} if ref $service; + $service = 'OSM' unless $service =~ /^(Bing|Google|OSM|Zurich)$/; + $service = 'OSM' if $service eq 'Bing' && !FixMyStreet->config('BING_MAPS_API_KEY'); + $service = "FixMyStreet::Geocode::${service}::string"; + + no strict 'refs'; + return &$service($s, $c); +} + +# escape STRING CONTEXT +# Escapes string for putting in URL geocoding call +sub escape { + my ($s, $c) = @_; $s = lc($s); $s =~ s/[^-&\w ']/ /g; $s =~ s/\s+/ /g; $s = URI::Escape::uri_escape_utf8($s); $s =~ s/%20/+/g; - my $params = $c->cobrand->disambiguate_location($s); - return FixMyStreet::Geocode::Bing::string($s, $c, $params) - if FixMyStreet->config('BING_MAPS_API_KEY'); - # Fall back to Google API, which allow access with and without a key - return FixMyStreet::Geocode::Google::string($s, $c, $params) - if FixMyStreet->config('GOOGLE_MAPS_API_KEY'); - return FixMyStreet::Geocode::OSM::string($s, $c, $params); + return $s; } 1; diff --git a/perllib/FixMyStreet/Geocode/Bing.pm b/perllib/FixMyStreet/Geocode/Bing.pm index 18e6b56ce..85eef3d0f 100644 --- a/perllib/FixMyStreet/Geocode/Bing.pm +++ b/perllib/FixMyStreet/Geocode/Bing.pm @@ -15,14 +15,21 @@ use File::Path (); use LWP::Simple; use Digest::MD5 qw(md5_hex); +use mySociety::Locale; + # string STRING CONTEXT # Looks up on Bing Maps API, and caches, a user-inputted location. # Returns array of (LAT, LON, ERROR), where ERROR is either undef, a string, or # an array of matches if there are more than one. The information in the query # may be used to disambiguate the location in cobranded versions of the site. sub string { - my ( $s, $c, $params ) = @_; + my ( $s, $c ) = @_; + + my $params = $c->cobrand->disambiguate_location($s); + + $s = FixMyStreet::Geocode::escape($s); $s .= '+' . $params->{town} if $params->{town} and $s !~ /$params->{town}/i; + my $url = "http://dev.virtualearth.net/REST/v1/Locations?q=$s"; $url .= '&userMapView=' . join(',', @{$params->{bounds}}) if $params->{bounds}; diff --git a/perllib/FixMyStreet/Geocode/Google.pm b/perllib/FixMyStreet/Geocode/Google.pm index db3a8ae91..fd65b89b1 100644 --- a/perllib/FixMyStreet/Geocode/Google.pm +++ b/perllib/FixMyStreet/Geocode/Google.pm @@ -14,6 +14,7 @@ use File::Slurp; use File::Path (); use LWP::Simple; use Digest::MD5 qw(md5_hex); +use mySociety::Locale; # string STRING CONTEXT # Looks up on Google Maps API, and caches, a user-inputted location. @@ -21,7 +22,11 @@ use Digest::MD5 qw(md5_hex); # an array of matches if there are more than one. The information in the query # may be used to disambiguate the location in cobranded versions of the site. sub string { - my ( $s, $c, $params ) = @_; + my ( $s, $c ) = @_; + + my $params = $c->cobrand->disambiguate_location($s); + + $s = FixMyStreet::Geocode::escape($s); my $url = 'http://maps.google.com/maps/geo?q=' . $s; $url .= '&ll=' . $params->{centre} if $params->{centre}; diff --git a/perllib/FixMyStreet/Geocode/OSM.pm b/perllib/FixMyStreet/Geocode/OSM.pm index d96338c16..fd14b0acc 100644 --- a/perllib/FixMyStreet/Geocode/OSM.pm +++ b/perllib/FixMyStreet/Geocode/OSM.pm @@ -29,8 +29,13 @@ my $nominatimbase = "http://nominatim.openstreetmap.org/"; # an array of matches if there are more than one. The information in the query # may be used to disambiguate the location in cobranded versions of the site. sub string { - my ( $s, $c, $params ) = @_; + my ( $s, $c ) = @_; + + my $params = $c->cobrand->disambiguate_location($s); + + $s = FixMyStreet::Geocode::escape($s); $s .= '+' . $params->{town} if $params->{town} and $s !~ /$params->{town}/i; + my $url = "${nominatimbase}search?"; my %query_params = ( q => $s, diff --git a/perllib/FixMyStreet/Geocode/Zurich.pm b/perllib/FixMyStreet/Geocode/Zurich.pm new file mode 100644 index 000000000..5aaca2c8e --- /dev/null +++ b/perllib/FixMyStreet/Geocode/Zurich.pm @@ -0,0 +1,107 @@ +#!/usr/bin/perl +# +# FixMyStreet::Geocode::Zurich +# Geocoding with Zurich web service. +# +# Thanks to http://msdn.microsoft.com/en-us/library/ms995764.aspx +# and http://noisemore.wordpress.com/2009/03/19/perl-soaplite-wsse-web-services-security-soapheader/ +# for SOAP::Lite pointers +# +# Copyright (c) 2012 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Geocode::Zurich; + +use strict; +use Digest::MD5 qw(md5_hex); +use File::Path (); +use Geo::Coordinates::CH1903; +use SOAP::Lite; +use Storable; +use mySociety::Locale; + +my ($soap, $method, $security); + +sub setup_soap { + return if $soap; + + # Variables for the SOAP web service + my $geocoder = FixMyStreet->config('GEOCODER'); + my $url = $geocoder->{url}; + my $username = $geocoder->{username}; + my $password = $geocoder->{password}; + my $attr = 'http://ch/geoz/fixmyzuerich/service'; + my $action = "$attr/IFixMyZuerich/"; + + # Set up the SOAP handler + $security = SOAP::Header->name("Security")->attr({ + 'mustUnderstand' => 'true', + 'xmlns' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' + })->value( + \SOAP::Header->name( + "UsernameToken" => \SOAP::Header->value( + SOAP::Header->name('Username', $username), + SOAP::Header->name('Password', $password) + ) + ) + ); + $soap = SOAP::Lite->on_action( sub { $action . $_[1]; } )->proxy($url); + $method = SOAP::Data->name('getLocation')->attr({ xmlns => $attr }); +} + +# string STRING CONTEXT +# Looks up on Zurich web service a user-inputted location. +# Returns array of (LAT, LON, ERROR), where ERROR is either undef, a string, or +# an array of matches if there are more than one. The information in the query +# may be used to disambiguate the location in cobranded versions of the site. +sub string { + my ( $s, $c ) = @_; + + setup_soap(); + + my $cache_dir = FixMyStreet->config('GEO_CACHE') . 'zurich/'; + my $cache_file = $cache_dir . md5_hex($s); + my $result; + if (-s $cache_file) { + $result = retrieve($cache_file); + } else { + my $search = SOAP::Data->name('search' => $s)->type(''); + my $count = SOAP::Data->name('count' => 10)->type(''); + eval { + $result = $soap->call($method, $security, $search, $count); + }; + if ($@) { + return { error => 'The geocoder appears to be down.' }; + } + $result = $result->result; + File::Path::mkpath($cache_dir); + store $result, $cache_file if $result; + } + + if (!$result || !$result->{Location}) { + return { error => _('Sorry, we could not parse that location. Please try again.') }; + } + + my $results = $result->{Location}; + $results = [ $results ] unless ref $results eq 'ARRAY'; + + my ( $error, @valid_locations, $latitude, $longitude ); + foreach (@$results) { + ($latitude, $longitude) = Geo::Coordinates::CH1903::to_latlon($_->{easting}, $_->{northing}); + mySociety::Locale::in_gb_locale { + push (@$error, { + address => $_->{text}, + latitude => sprintf('%0.6f', $latitude), + longitude => sprintf('%0.6f', $longitude) + }); + }; + push (@valid_locations, $_); + last if lc($_->{text}) eq lc($s); + } + + return { latitude => $latitude, longitude => $longitude } if scalar @valid_locations == 1; + return { error => $error }; +} + +1; + diff --git a/perllib/FixMyStreet/Map.pm b/perllib/FixMyStreet/Map.pm index 587c63d25..a1876e150 100644 --- a/perllib/FixMyStreet/Map.pm +++ b/perllib/FixMyStreet/Map.pm @@ -114,7 +114,26 @@ sub _map_features { } sub map_pins { - return $map_class->map_pins(@_); + my ($c, $interval) = @_; + + my $bbox = $c->req->param('bbox'); + my ( $min_lon, $min_lat, $max_lon, $max_lat ) = split /,/, $bbox; + + my ( $around_map, $around_map_list, $nearby, $dist ) = + FixMyStreet::Map::map_features_bounds( $c, $min_lon, $min_lat, $max_lon, $max_lat, $interval ); + + # create a list of all the pins + my @pins = map { + # Here we might have a DB::Problem or a DB::Nearby, we always want the problem. + my $p = (ref $_ eq 'FixMyStreet::App::Model::DB::Nearby') ? $_->problem : $_; + my $colour = $c->cobrand->pin_colour( $p, 'around' ); + [ $p->latitude, $p->longitude, + $colour, + $p->id, $p->title + ] + } @$around_map, @$nearby; + + return (\@pins, $around_map_list, $nearby, $dist); } sub click_to_wgs84 { diff --git a/perllib/FixMyStreet/Map/OSM.pm b/perllib/FixMyStreet/Map/OSM.pm index 6b3bebba2..d8abc9dd6 100644 --- a/perllib/FixMyStreet/Map/OSM.pm +++ b/perllib/FixMyStreet/Map/OSM.pm @@ -94,29 +94,6 @@ sub display_map { }; } -sub map_pins { - my ($self, $c, $interval) = @_; - - my $bbox = $c->req->param('bbox'); - my ( $min_lon, $min_lat, $max_lon, $max_lat ) = split /,/, $bbox; - - my ( $around_map, $around_map_list, $nearby, $dist ) = - FixMyStreet::Map::map_features_bounds( $c, $min_lon, $min_lat, $max_lon, $max_lat, $interval ); - - # create a list of all the pins - my @pins = map { - # Here we might have a DB::Problem or a DB::Nearby, we always want the problem. - my $p = (ref $_ eq 'FixMyStreet::App::Model::DB::Nearby') ? $_->problem : $_; - my $colour = $c->cobrand->pin_colour( $p, 'around' ); - [ $p->latitude, $p->longitude, - $colour, - $p->id, $p->title - ] - } @$around_map, @$nearby; - - return (\@pins, $around_map_list, $nearby, $dist); -} - sub compass { my ( $x, $y, $z ) = @_; return { diff --git a/perllib/FixMyStreet/Map/Zurich.pm b/perllib/FixMyStreet/Map/Zurich.pm new file mode 100644 index 000000000..d2f7a35af --- /dev/null +++ b/perllib/FixMyStreet/Map/Zurich.pm @@ -0,0 +1,177 @@ +#!/usr/bin/perl +# +# FixMyStreet:Map::Zurich +# Zurich have their own tileserver. +# +# Copyright (c) 2012 UK Citizens Online Democracy. All rights reserved. +# Email: steve@mysociety.org; WWW: http://www.mysociety.org/ + +package FixMyStreet::Map::Zurich; + +use strict; +use Geo::Coordinates::CH1903; +use Math::Trig; +use Utils; + +use constant ZOOM_LEVELS => 10; +use constant DEFAULT_ZOOM => 7; +use constant MIN_ZOOM_LEVEL => 0; + +sub map_tiles { + my ( $self, %params ) = @_; + my ( $col, $row, $z ) = ( $params{x_tile}, $params{y_tile}, $params{matrix_id} ); + my $tile_url = $self->base_tile_url(); + return [ + "$tile_url/$z/" . ($row - 1) . "/" . ($col - 1) . ".jpg", + "$tile_url/$z/" . ($row - 1) . "/$col.jpg", + "$tile_url/$z/$row/" . ($col - 1) . ".jpg", + "$tile_url/$z/$row/$col.jpg", + ]; +} + +sub base_tile_url { + return 'http://www.wmts.stadt-zuerich.ch/Luftbild/MapServer/WMTS/tile/1.0.0/Luftbild/default/nativeTileMatrixSet'; +} + +sub copyright { + return '© Stadt Zürich'; +} + +# display_map C PARAMS +# PARAMS include: +# latitude, longitude for the centre point of the map +# CLICKABLE is set if the map is clickable +# PINS is array of pins to show, location and colour +sub display_map { + my ($self, $c, %params) = @_; + + my $numZoomLevels = ZOOM_LEVELS; + my $zoomOffset = MIN_ZOOM_LEVEL; +# if ($params{any_zoom}) { +# $numZoomLevels = 10; +# $zoomOffset = 0; +# } + + # TODO Adjust zoom level dependent upon population density + my $default_zoom = DEFAULT_ZOOM; + + # Map centre may be overridden in the query string + $params{latitude} = Utils::truncate_coordinate($c->req->params->{lat} + 0) + if defined $c->req->params->{lat}; + $params{longitude} = Utils::truncate_coordinate($c->req->params->{lon} + 0) + if defined $c->req->params->{lon}; + + my $zoom = defined $c->req->params->{zoom} ? $c->req->params->{zoom} + 0 : $default_zoom; + $zoom = $numZoomLevels - 1 if $zoom >= $numZoomLevels; + $zoom = 0 if $zoom < 0; + $params{zoom_act} = $zoomOffset + $zoom; + + ($params{x_tile}, $params{y_tile}, $params{matrix_id}) = latlon_to_tile_with_adjust($params{latitude}, $params{longitude}, $params{zoom_act}); + + foreach my $pin (@{$params{pins}}) { + ($pin->{px}, $pin->{py}) = latlon_to_px($pin->{latitude}, $pin->{longitude}, $params{x_tile}, $params{y_tile}, $params{zoom_act}); + } + + $c->stash->{map} = { + %params, + type => 'zurich', + map_type => 'OpenLayers.Layer.WMTS', + tiles => $self->map_tiles( %params ), + copyright => $self->copyright(), + zoom => $zoom, + zoomOffset => $zoomOffset, + numZoomLevels => $numZoomLevels, + }; +} + +# Given a lat/lon, convert it to Zurch tile co-ordinates (precise). +sub latlon_to_tile($$$) { + my ($lat, $lon, $zoom) = @_; + + my ($x, $y) = Geo::Coordinates::CH1903::from_latlon($lat, $lon); + + my $matrix_id = $zoom - 1; + $matrix_id = 0 if $matrix_id < 0; + + my @scales = ( '250000', '125000', '64000', '32000', '16000', '8000', '4000', '2000', '1000', '500' ); + my $tileOrigin = { lat => 30814423, lon => -29386322 }; + my $tileSize = 256; + my $res = $scales[$zoom] / (39.3701 * 96); # OpenLayers.INCHES_PER_UNIT[units] * OpenLayers.DOTS_PER_INCH + + my $fx = ( $x - $tileOrigin->{lon} ) / ($res * $tileSize); + my $fy = ( $tileOrigin->{lat} - $y ) / ($res * $tileSize); + + return ( $fx, $fy, $matrix_id ); +} + +# Given a lat/lon, convert it to OSM tile co-ordinates (nearest actual tile, +# adjusted so the point will be near the centre of a 2x2 tiled map). +sub latlon_to_tile_with_adjust($$$) { + my ($lat, $lon, $zoom) = @_; + my ($x_tile, $y_tile, $matrix_id) = latlon_to_tile($lat, $lon, $zoom); + + # Try and have point near centre of map + if ($x_tile - int($x_tile) > 0.5) { + $x_tile += 1; + } + if ($y_tile - int($y_tile) > 0.5) { + $y_tile += 1; + } + + return ( int($x_tile), int($y_tile), $matrix_id ); +} + +sub tile_to_latlon { + my ($fx, $fy, $zoom) = @_; + + my @scales = ( '250000', '125000', '64000', '32000', '16000', '8000', '4000', '2000', '1000', '500' ); + my $tileOrigin = { lat => 30814423, lon => -29386322 }; + my $tileSize = 256; + my $res = $scales[$zoom] / (39.3701 * 96); # OpenLayers.INCHES_PER_UNIT[units] * OpenLayers.DOTS_PER_INCH + + my $x = $fx * $res * $tileSize + $tileOrigin->{lon}; + my $y = $tileOrigin->{lat} - $fy * $res * $tileSize; + + my ($lat, $lon) = Geo::Coordinates::CH1903::to_latlon($x, $y); + + return ( $lat, $lon ); +} + +# Given a lat/lon, convert it to pixel co-ordinates from the top left of the map +sub latlon_to_px($$$$$) { + my ($lat, $lon, $x_tile, $y_tile, $zoom) = @_; + my ($pin_x_tile, $pin_y_tile) = latlon_to_tile($lat, $lon, $zoom); + my $pin_x = tile_to_px($pin_x_tile, $x_tile); + my $pin_y = tile_to_px($pin_y_tile, $y_tile); + return ($pin_x, $pin_y); +} + +# 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) = @_; + $p = 256 * ($p - $c + 1); + $p = int($p + .5 * ($p <=> 0)); + return $p; +} + +sub click_to_tile { + my ($pin_tile, $pin) = @_; + $pin -= 256 while $pin > 256; + $pin += 256 while $pin < 0; + return $pin_tile + $pin / 256; +} + +# Given some click co-ords (the tile they were on, and where in the +# tile they were), convert to WGS84 and return. +# XXX Note use of MIN_ZOOM_LEVEL here. (Copied from OSM, needed here?) +sub click_to_wgs84 { + my ($self, $c, $pin_tile_x, $pin_x, $pin_tile_y, $pin_y) = @_; + my $tile_x = click_to_tile($pin_tile_x, $pin_x); + my $tile_y = click_to_tile($pin_tile_y, $pin_y); + my $zoom = MIN_ZOOM_LEVEL + (defined $c->req->params->{zoom} ? $c->req->params->{zoom} : DEFAULT_ZOOM); + my ($lat, $lon) = tile_to_latlon($tile_x, $tile_y, $zoom); + return ( $lat, $lon ); +} + +1; |