aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet
diff options
context:
space:
mode:
Diffstat (limited to 'perllib/FixMyStreet')
-rw-r--r--perllib/FixMyStreet/App/Controller/Location.pm1
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm8
-rw-r--r--perllib/FixMyStreet/Cobrand/Bromley.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm1
-rw-r--r--perllib/FixMyStreet/Cobrand/Zurich.pm16
-rw-r--r--perllib/FixMyStreet/Geocode.pm24
-rw-r--r--perllib/FixMyStreet/Geocode/Bing.pm9
-rw-r--r--perllib/FixMyStreet/Geocode/Google.pm7
-rw-r--r--perllib/FixMyStreet/Geocode/OSM.pm7
-rw-r--r--perllib/FixMyStreet/Geocode/Zurich.pm107
-rw-r--r--perllib/FixMyStreet/Map.pm21
-rw-r--r--perllib/FixMyStreet/Map/OSM.pm23
-rw-r--r--perllib/FixMyStreet/Map/Zurich.pm177
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;