diff options
-rw-r--r-- | perllib/FixMyStreet/Map/Zurich.pm | 175 | ||||
-rw-r--r-- | perllib/Geo/Coordinates/CH1903.pm | 115 |
2 files changed, 272 insertions, 18 deletions
diff --git a/perllib/FixMyStreet/Map/Zurich.pm b/perllib/FixMyStreet/Map/Zurich.pm index 3d343c6ac..d2f7a35af 100644 --- a/perllib/FixMyStreet/Map/Zurich.pm +++ b/perllib/FixMyStreet/Map/Zurich.pm @@ -7,32 +7,171 @@ # Email: steve@mysociety.org; WWW: http://www.mysociety.org/ package FixMyStreet::Map::Zurich; -use base 'FixMyStreet::Map::OSM'; use strict; +use Geo::Coordinates::CH1903; +use Math::Trig; +use Utils; -sub map_type { - return 'OpenLayers.Layer.WMTS'; -} - -sub map_template { - return 'zurich'; -} - -sub base_tile_url { - return 'http://www.wmts.stadt-zuerich.ch/Luftbild/MapServer/WMTS/tile/1.0.0/Luftbild/default/default028mm'; -} +use constant ZOOM_LEVELS => 10; +use constant DEFAULT_ZOOM => 7; +use constant MIN_ZOOM_LEVEL => 0; sub map_tiles { my ( $self, %params ) = @_; - my ( $x, $y, $z ) = ( $params{x_tile}, $params{y_tile}, $params{zoom_act} ); + my ( $col, $row, $z ) = ( $params{x_tile}, $params{y_tile}, $params{matrix_id} ); my $tile_url = $self->base_tile_url(); return [ - "$tile_url/$z/" . ($x - 1) . "/" . ($y - 1) . ".jpg", - "$tile_url/$z/$x/" . ($y - 1) . ".jpg", - "$tile_url/$z/" . ($x - 1) . "/$y.jpg", - "$tile_url/$z/$x/$y.jpg", + "$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", ]; } -1;
\ No newline at end of file +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; diff --git a/perllib/Geo/Coordinates/CH1903.pm b/perllib/Geo/Coordinates/CH1903.pm new file mode 100644 index 000000000..611126962 --- /dev/null +++ b/perllib/Geo/Coordinates/CH1903.pm @@ -0,0 +1,115 @@ +#!/usr/bin/perl +# +# Geo::Coordinates::CH1903 +# Conversion between WGS84 and Swiss CH1903. +# +# Copyright (c) 2012 UK Citizens Online Democracy. This module is free +# software; you can redistribute it and/or modify it under the same terms as +# Perl itself. +# +# WWW: http://www.mysociety.org/ + +package Geo::Coordinates::CH1903; + +$Geo::Coordinates::CH1903::VERSION = '1.00'; + +use strict; + +=head1 NAME + +Geo::Coordinates::CH1903 + +=head1 VERSION + +1.00 + +=head1 SYNOPSIS + + use Geo::Coordinates::CH1903; + + my ($lat, $lon) = ...; + my ($e, $n) = Geo::Coordinates::CH1903::from_latlon($lat, $lon); + my ($lat, $lon) = Geo::Coordinates::CH1903::to_latlon($e, $n); + +=head1 FUNCTIONS + +=over 4 + +=cut + +sub deg2sec($) { + my $angle = shift; + + my $deg = int($angle); + my $min = int(($angle - $deg) * 60); + my $sec = ((($angle - $deg) * 60) - $min) * 60; + + return $sec + ($min * 60) + ($deg * 3600); +} + +sub from_latlon($$) { + my ($lat, $lon) = @_; + + $lat = deg2sec($lat); + $lon = deg2sec($lon); + + my $lat_aux = ($lat - 169028.66) / 10000; + my $lon_aux = ($lon - 26782.5) / 10000; + + my $x = 600072.37 + + (211455.93 * $lon_aux) + - (10938.51 * $lon_aux * $lat_aux) + - (0.36 * $lon_aux * $lat_aux**2) + - (44.54 * $lon_aux**3); + + my $y = 200147.07 + + (308807.95 * $lat_aux) + + (3745.25 * $lon_aux**2) + + (76.63 * $lat_aux**2) + - (194.56 * $lon_aux**2 * $lat_aux) + + (119.79 * $lat_aux**3); + + return ($x, $y); +} + +sub to_latlon($$) { + my ($x, $y) = @_; + + my $x_aux = ($x - 600000) / 1000000; + my $y_aux = ($y - 200000) / 1000000; + + my $lat = 16.9023892 + + (3.238272 * $y_aux) + - (0.270978 * $x_aux**2) + - (0.002528 * $y_aux**2) + - (0.0447 * $x_aux**2 * $y_aux) + - (0.0140 * $y_aux**3); + + my $lon = 2.6779094 + + (4.728982 * $x_aux) + + (0.791484 * $x_aux * $y_aux) + + (0.1306 * $x_aux * $y_aux**2) + - (0.0436 * $x_aux**3); + + $lat = $lat * 100 / 36; + $lon = $lon * 100 / 36; + + return ($lat, $lon); +} + +=head1 AUTHOR AND COPYRIGHT + +Maths courtesy of the Swiss Federal Office of Topography: +http://www.swisstopo.admin.ch/internet/swisstopo/en/home/products/software/products/skripts.html + +Written by Matthew Somerville + +Copyright (c) UK Citizens Online Democracy. + +This module is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=cut + +1; + |