diff options
Diffstat (limited to 'perllib/FixMyStreet/Map')
-rw-r--r-- | perllib/FixMyStreet/Map/BathNES.pm | 20 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/Bristol.pm | 54 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/Bromley.pm | 12 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/Buckinghamshire.pm | 41 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/CheshireEast.pm | 70 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/GoogleOL.pm | 2 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/HighwaysEngland.pm | 8 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/Hounslow.pm | 63 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/IsleOfWight.pm | 63 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/Lincolnshire.pm | 21 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/MasterMap.pm | 40 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/Northamptonshire.pm | 85 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/OSM.pm | 40 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/UKCouncilWMTS.pm | 55 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/WMSBase.pm | 151 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/WMTSBase.pm | 195 | ||||
-rw-r--r-- | perllib/FixMyStreet/Map/WMXBase.pm | 199 |
17 files changed, 819 insertions, 300 deletions
diff --git a/perllib/FixMyStreet/Map/BathNES.pm b/perllib/FixMyStreet/Map/BathNES.pm deleted file mode 100644 index 45261a625..000000000 --- a/perllib/FixMyStreet/Map/BathNES.pm +++ /dev/null @@ -1,20 +0,0 @@ -# FixMyStreet:Map::BathNES -# More JavaScript, for street assets - -package FixMyStreet::Map::BathNES; -use base 'FixMyStreet::Map::OSM'; - -use strict; - -sub map_javascript { [ - '/vendor/OpenLayers/OpenLayers.wfs.js', - '/vendor/OpenLayers.Projection.OrdnanceSurvey.js', - '/js/map-OpenLayers.js', - '/js/map-OpenStreetMap.js', - '/cobrands/fixmystreet-uk-councils/roadworks.js', - '/cobrands/fixmystreet/assets.js', - '/cobrands/bathnes/js.js', - '/cobrands/bathnes/assets.js', -] } - -1; diff --git a/perllib/FixMyStreet/Map/Bristol.pm b/perllib/FixMyStreet/Map/Bristol.pm index 99bdd26d7..70240a991 100644 --- a/perllib/FixMyStreet/Map/Bristol.pm +++ b/perllib/FixMyStreet/Map/Bristol.pm @@ -2,39 +2,15 @@ # Bristol use their own tiles on their cobrand package FixMyStreet::Map::Bristol; -use base 'FixMyStreet::Map::WMTSBase'; +use base 'FixMyStreet::Map::UKCouncilWMTS'; use strict; -sub zoom_parameters { - my $self = shift; - my $params = { - zoom_levels => scalar $self->scales, - default_zoom => 5, - min_zoom_level => 0, - id_offset => 0, - }; - return $params; -} +sub default_zoom { 5; } -sub tile_parameters { - my $self = shift; - my $params = { - urls => [ 'https://maps.bristol.gov.uk/arcgis/rest/services/base/2015_BCC_96dpi/MapServer/WMTS/tile' ], - layer_names => [ '2015_BCC_96dpi' ], - wmts_version => '1.0.0', - layer_style => 'default', - matrix_set => 'default028mm', - suffix => '.png', # appended to tile URLs - size => 256, # pixels - dpi => 96, - inches_per_unit => 39.3701, # BNG uses metres - projection => 'EPSG:27700', - origin_x => -5220400.0, - origin_y => 4470200.0, - }; - return $params; -} +sub urls { [ 'https://maps.bristol.gov.uk/arcgis/rest/services/base/2019_Q2_BCC_96dpi/MapServer/WMTS/tile' ] } + +sub layer_names { [ '2019_Q2_BCC_96dpi' ] } sub scales { my $self = shift; @@ -56,29 +32,11 @@ sub copyright { return '© BCC'; } -sub map_template { 'bristol' } - sub map_javascript { [ - '/vendor/OpenLayers/OpenLayers.bristol.js', + '/vendor/OpenLayers/OpenLayers.wmts.js', '/js/map-OpenLayers.js', '/js/map-wmts-base.js', '/js/map-wmts-bristol.js', - '/cobrands/fixmystreet/assets.js', - '/cobrands/bristol/assets.js', ] } -# Reproject a WGS84 lat/lon into BNG easting/northing -sub reproject_from_latlon($$$) { - my ($self, $lat, $lon) = @_; - my ($x, $y) = Utils::convert_latlon_to_en($lat, $lon); - return ($x, $y); -} - -# Reproject a BNG easting/northing into WGS84 lat/lon -sub reproject_to_latlon($$$) { - my ($self, $x, $y) = @_; - my ($lat, $lon) = Utils::convert_en_to_latlon($x, $y); - return ($lat, $lon); -} - 1; diff --git a/perllib/FixMyStreet/Map/Bromley.pm b/perllib/FixMyStreet/Map/Bromley.pm index cd50cc1d1..518382fc0 100644 --- a/perllib/FixMyStreet/Map/Bromley.pm +++ b/perllib/FixMyStreet/Map/Bromley.pm @@ -9,18 +9,8 @@ use base 'FixMyStreet::Map::FMS'; use strict; -sub map_javascript { [ - '/vendor/OpenLayers/OpenLayers.wfs.js', - '/js/map-OpenLayers.js', - '/js/map-bing-ol.js', - '/js/map-fms.js', - '/cobrands/fixmystreet/assets.js', - '/cobrands/bromley/map.js', - '/cobrands/bromley/assets.js', -] } - sub map_tile_base { - '-', "https://%sfix.bromley.gov.uk/tilma/%d/%d/%d.png"; + '-', "//%stilma.mysociety.org/bromley/%d/%d/%d.png"; } 1; diff --git a/perllib/FixMyStreet/Map/Buckinghamshire.pm b/perllib/FixMyStreet/Map/Buckinghamshire.pm index 10ee2a080..4c1b8e9a0 100644 --- a/perllib/FixMyStreet/Map/Buckinghamshire.pm +++ b/perllib/FixMyStreet/Map/Buckinghamshire.pm @@ -2,19 +2,44 @@ # More JavaScript, for street assets package FixMyStreet::Map::Buckinghamshire; -use base 'FixMyStreet::Map::OSM'; +use base 'FixMyStreet::Map::UKCouncilWMTS'; use strict; +sub default_zoom { 8; } + +sub urls { [ 'https://maps.buckscc.gov.uk/arcgis/rest/services/Bucks_Basemapping/MapServer/WMTS/tile' ] } + +sub layer_names { [ 'Bucks_Basemapping' ] } + +sub scales { + my $self = shift; + my @scales = ( + '1000000', + '500000', + '250000', + '125000', + '64000', + '32000', + '16000', + '8000', + '4000', + '2000', + '1000', + ); + return @scales; + +} + +sub copyright { + return '© BCC'; +} + sub map_javascript { [ - '/vendor/OpenLayers/OpenLayers.wfs.js', - '/vendor/OpenLayers.Projection.OrdnanceSurvey.js', + '/vendor/OpenLayers/OpenLayers.wmts.js', '/js/map-OpenLayers.js', - '/js/map-OpenStreetMap.js', - '/cobrands/fixmystreet-uk-councils/roadworks.js', - '/cobrands/fixmystreet/assets.js', - '/cobrands/buckinghamshire/js.js', - '/cobrands/buckinghamshire/assets.js', + '/js/map-wmts-base.js', + '/js/map-wmts-buckinghamshire.js', ] } 1; diff --git a/perllib/FixMyStreet/Map/CheshireEast.pm b/perllib/FixMyStreet/Map/CheshireEast.pm new file mode 100644 index 000000000..4e59f2593 --- /dev/null +++ b/perllib/FixMyStreet/Map/CheshireEast.pm @@ -0,0 +1,70 @@ +package FixMyStreet::Map::CheshireEast; +use base 'FixMyStreet::Map::OSM'; + +use strict; +use Utils; + +use constant MIN_ZOOM_LEVEL => 7; + +sub map_javascript { [ + '/vendor/OpenLayers/OpenLayers.wfs.js', + '/js/map-OpenLayers.js', + '/js/map-cheshireeast.js', +] } + +sub tile_parameters { { + origin_x => -3276800, + origin_y => 3276800, +} } + +sub resolutions { ( + 1792.003584007169, + 896.0017920035843, + 448.0008960017922, + 224.0004480008961, + 112.000224000448, + 56.000112000224014, + 28.000056000111993, + 14.000028000056004, + 7.000014000028002, + 2.8000056000112004, + 1.4000028000056002, + 0.7000014000028001, + 0.35000070000140004, + 0.14000028000056003, +) } + +my $url = 'https://maps-cache.cheshiresharedservices.gov.uk/maps/?wmts/CE_OS_AllBasemaps_COLOUR/oscce_grid/%d/%d/%d.jpeg&KEY=3a3f5c60eca1404ea114e6941c9d3895'; + +sub map_tiles { + my ( $self, %params ) = @_; + my ( $x, $y, $z ) = ( $params{x_tile}, $params{y_tile}, $params{zoom_act} ); + return [ + sprintf($url, $z, $x-1, $y-1), + sprintf($url, $z, $x, $y-1), + sprintf($url, $z, $x-1, $y), + sprintf($url, $z, $x, $y), + ]; +} + +sub latlon_to_tile($$$$) { + my ($self, $lat, $lon, $zoom) = @_; + my ($x, $y) = eval { Utils::convert_latlon_to_en($lat, $lon) }; + my $tile_params = $self->tile_parameters; + my $res = ($self->resolutions)[$zoom]; + my $fx = ( $x - $tile_params->{origin_x} ) / ($res * 256); + my $fy = ( $tile_params->{origin_y} - $y ) / ($res * 256); + return ( $fx, $fy ); +} + +sub tile_to_latlon { + my ($self, $fx, $fy, $zoom) = @_; + my $tile_params = $self->tile_parameters; + my $res = ($self->resolutions)[$zoom]; + my $x = $fx * $res * 256 + $tile_params->{origin_x}; + my $y = $tile_params->{origin_y} - $fy * $res * 256; + my ($lat, $lon) = Utils::convert_en_to_latlon($x, $y); + return ( $lat, $lon ); +} + +1; diff --git a/perllib/FixMyStreet/Map/GoogleOL.pm b/perllib/FixMyStreet/Map/GoogleOL.pm index 44d0e77e7..7049b27d4 100644 --- a/perllib/FixMyStreet/Map/GoogleOL.pm +++ b/perllib/FixMyStreet/Map/GoogleOL.pm @@ -16,7 +16,7 @@ sub map_template { 'google-ol' } sub map_javascript { my $google_maps_url = "https://maps.googleapis.com/maps/api/js?v=3"; my $key = FixMyStreet->config('GOOGLE_MAPS_API_KEY'); - $google_maps_url .= "&key=$key" if $key; + $google_maps_url .= "&key=$key" if $key; [ $google_maps_url, '/vendor/OpenLayers/OpenLayers.google.js', diff --git a/perllib/FixMyStreet/Map/HighwaysEngland.pm b/perllib/FixMyStreet/Map/HighwaysEngland.pm new file mode 100644 index 000000000..de44240a4 --- /dev/null +++ b/perllib/FixMyStreet/Map/HighwaysEngland.pm @@ -0,0 +1,8 @@ +package FixMyStreet::Map::HighwaysEngland; +use base 'FixMyStreet::Map::FMS'; + +use strict; + +use constant MIN_ZOOM_LEVEL => 12; + +1; diff --git a/perllib/FixMyStreet/Map/Hounslow.pm b/perllib/FixMyStreet/Map/Hounslow.pm new file mode 100644 index 000000000..d66188a83 --- /dev/null +++ b/perllib/FixMyStreet/Map/Hounslow.pm @@ -0,0 +1,63 @@ +# FixMyStreet:Map::Hounslow +# Hounslow use their own tiles on their cobrand + +package FixMyStreet::Map::Hounslow; +use base 'FixMyStreet::Map::UKCouncilWMTS'; + +use strict; + +sub default_zoom { 5; } + +sub urls { [ 'https://gis.ringway.co.uk/server/rest/services/Hosted/HounslowOSBasemap/MapServer/WMTS/tile' ] } + +sub layer_names { [ 'Hosted_HounslowOSBasemap' ] } + +sub scales { + my $self = shift; + my @scales = ( + # The first 5 levels don't load and are really zoomed-out, so + # they're not included here. + # '600000', + # '500000', + # '400000', + # '300000', + # '200000', + '100000', + '75000', + '50000', + '25000', + '10000', + '8000', + '6000', + '4000', + '2000', + '1000', + '400', + ); + return @scales; +} + +sub zoom_parameters { + my $self = shift; + my $params = { + zoom_levels => scalar $self->scales, + default_zoom => $self->default_zoom, + min_zoom_level => 0, + id_offset => 5, # see note above about zoom layers we've skipped + }; + return $params; +} + +sub copyright { + return 'Contains Ordnance Survey data © Crown copyright and database rights 2019 OS. Use of this data is subject to <a href="/about/mapterms">terms and conditions</a>.'; +} + + +sub map_javascript { [ + '/vendor/OpenLayers/OpenLayers.wmts.js', + '/js/map-OpenLayers.js', + '/js/map-wmts-base.js', + '/js/map-wmts-hounslow.js', +] } + +1; diff --git a/perllib/FixMyStreet/Map/IsleOfWight.pm b/perllib/FixMyStreet/Map/IsleOfWight.pm new file mode 100644 index 000000000..2316e2939 --- /dev/null +++ b/perllib/FixMyStreet/Map/IsleOfWight.pm @@ -0,0 +1,63 @@ +# FixMyStreet:Map::IsleOfWight +# IsleOfWight use their own tiles on their cobrand + +package FixMyStreet::Map::IsleOfWight; +use base 'FixMyStreet::Map::UKCouncilWMTS'; + +use strict; + +sub default_zoom { 7; } + +sub urls { [ 'https://gis.ringway.co.uk/server/rest/services/Hosted/IOW_OS/MapServer/WMTS/tile' ] } + +sub layer_names { [ 'Hosted_IOW_OS' ] } + +sub scales { + my $self = shift; + my @scales = ( + # The first 5 levels don't load and are really zoomed-out, so + # they're not included here. + # '600000', + # '500000', + # '400000', + # '300000', + # '200000', + '100000', + '75000', + '50000', + '25000', + '10000', + '8000', + '6000', + '4000', + '2000', + '1000', + '400', + ); + return @scales; +} + +sub zoom_parameters { + my $self = shift; + my $params = { + zoom_levels => scalar $self->scales, + default_zoom => $self->default_zoom, + min_zoom_level => 0, + id_offset => 5, # see note above about zoom layers we've skipped + }; + return $params; +} + +sub copyright { + return 'Contains Ordnance Survey data © Crown copyright and database rights 2019 OS 100019229. Use of this data is subject to <a href="/about/mapterms">terms and conditions</a>.'; +} + + +sub map_javascript { [ + '/vendor/OpenLayers/OpenLayers.wmts.js', + '/js/map-OpenLayers.js', + '/js/map-wmts-base.js', + '/js/map-wmts-isleofwight.js', +] } + +1; diff --git a/perllib/FixMyStreet/Map/Lincolnshire.pm b/perllib/FixMyStreet/Map/Lincolnshire.pm deleted file mode 100644 index 7dbfe5d8e..000000000 --- a/perllib/FixMyStreet/Map/Lincolnshire.pm +++ /dev/null @@ -1,21 +0,0 @@ -# FixMyStreet:Map::Lincolnshire -# More JavaScript, for street assets - -package FixMyStreet::Map::Lincolnshire; -use base 'FixMyStreet::Map::FMS'; - -use strict; - -sub map_javascript { [ - '/vendor/OpenLayers/OpenLayers.wfs.js', - '/vendor/OpenLayers.Projection.OrdnanceSurvey.js', - '/js/map-OpenLayers.js', - '/js/map-bing-ol.js', - '/js/map-fms.js', - '/cobrands/fixmystreet-uk-councils/roadworks.js', - '/cobrands/fixmystreet/assets.js', - '/cobrands/lincolnshire/roadworks.js', - '/cobrands/lincolnshire/assets.js', -] } - -1; diff --git a/perllib/FixMyStreet/Map/MasterMap.pm b/perllib/FixMyStreet/Map/MasterMap.pm new file mode 100644 index 000000000..d66234bbf --- /dev/null +++ b/perllib/FixMyStreet/Map/MasterMap.pm @@ -0,0 +1,40 @@ +# FixMyStreet:Map::MasterMap +# +# A combination of FMS OS maps and our own tiles + +package FixMyStreet::Map::MasterMap; +use base 'FixMyStreet::Map::FMS'; + +use strict; + +use constant ZOOM_LEVELS => 7; + +sub map_template { 'fms' } + +sub map_javascript { [ + '/vendor/OpenLayers/OpenLayers.wfs.js', + '/js/map-OpenLayers.js', + '/js/map-bing-ol.js', + '/js/map-fms.js', + '/js/map-mastermap.js', +] } + +sub map_tiles { + my ( $self, %params ) = @_; + my ( $x, $y, $z ) = ( $params{x_tile}, $params{y_tile}, $params{zoom_act} ); + if ($z >= 17) { + my $layer = FixMyStreet->config('STAGING_SITE') ? 'mastermap-staging' : 'mastermap'; + my $base = "//%stilma.mysociety.org/$layer/%d/%d/%d.png"; + return [ + sprintf($base, 'a.', $z, $x-1, $y-1), + sprintf($base, 'b.', $z, $x, $y-1), + sprintf($base, 'c.', $z, $x-1, $y), + sprintf($base, '', $z, $x, $y), + ]; + } else { + return $self->SUPER::map_tiles(%params); + } +} + +1; + diff --git a/perllib/FixMyStreet/Map/Northamptonshire.pm b/perllib/FixMyStreet/Map/Northamptonshire.pm new file mode 100644 index 000000000..81f7e45eb --- /dev/null +++ b/perllib/FixMyStreet/Map/Northamptonshire.pm @@ -0,0 +1,85 @@ +package FixMyStreet::Map::Northamptonshire; +use base 'FixMyStreet::Map::WMSBase'; + +use strict; + +sub default_zoom { 8; } + +sub urls { [ 'https://maps.northamptonshire.gov.uk/interactivemappingwms/getmap.ashx' ] } + +sub layer_names{ [ 'BaseMap' ] } + +sub copyright { + return '© NCC'; +} + +sub scales { + my $self = shift; + my @scales = ( + '300000', + '200000', + '100000', + '75000', + '50000', + '25000', + '10000', + '8000', + '6000', + '4000', + '2000', + '1000', + '400', + ); + return @scales; +} +sub tile_parameters { + my $self = shift; + my $params = { + urls => $self->urls, + layer_names => $self->layer_names, + wms_version => '1.1.1', + layer_style => 'default', + format => 'image/png', # appended to tile URLs + size => 256, # pixels + dpi => 96, + inches_per_unit => 39.3701, + projection => 'EPSG:27700', + }; + return $params; +} + +sub zoom_parameters { + my $self = shift; + my $params = { + zoom_levels => scalar $self->scales, + default_zoom => 7, + min_zoom_level => 1, + id_offset => 0, + }; + return $params; +} + +# Reproject a WGS84 lat/lon into BNG easting/northing +sub reproject_from_latlon($$$) { + my ($self, $lat, $lon) = @_; + # do not try to reproject if we have no co-ordindates as convert breaks + return (0.0, 0.0) if $lat == 0 && $lon == 0; + my ($x, $y) = Utils::convert_latlon_to_en($lat, $lon); + return ($x, $y); +} + +# Reproject a BNG easting/northing into WGS84 lat/lon +sub reproject_to_latlon($$$) { + my ($self, $x, $y) = @_; + my ($lat, $lon) = Utils::convert_en_to_latlon($x, $y); + return ($lat, $lon); +} + +sub map_javascript { [ + '/vendor/OpenLayers/OpenLayers.wms.js', + '/js/map-OpenLayers.js', + '/js/map-wms-base.js', + '/js/map-wms-northamptonshire.js', +] } + +1; diff --git a/perllib/FixMyStreet/Map/OSM.pm b/perllib/FixMyStreet/Map/OSM.pm index a6cb6acea..ef465d7dc 100644 --- a/perllib/FixMyStreet/Map/OSM.pm +++ b/perllib/FixMyStreet/Map/OSM.pm @@ -87,10 +87,10 @@ sub generate_map_data { $zoom = $numZoomLevels - 1 if $zoom >= $numZoomLevels; $zoom = 0 if $zoom < 0; $params{zoom_act} = $zoomOffset + $zoom; - ($params{x_tile}, $params{y_tile}) = latlon_to_tile_with_adjust($params{latitude}, $params{longitude}, $params{zoom_act}); + ($params{x_tile}, $params{y_tile}) = $self->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}); + ($pin->{px}, $pin->{py}) = $self->latlon_to_px($pin->{latitude}, $pin->{longitude}, $params{x_tile}, $params{y_tile}, $params{zoom_act}); } return { @@ -102,24 +102,24 @@ sub generate_map_data { zoom => $zoom, zoomOffset => $zoomOffset, numZoomLevels => $numZoomLevels, - compass => compass( $params{x_tile}, $params{y_tile}, $params{zoom_act} ), + compass => $self->compass( $params{x_tile}, $params{y_tile}, $params{zoom_act} ), }; } sub compass { - my ( $x, $y, $z ) = @_; + my ( $self, $x, $y, $z ) = @_; return { - north => [ map { Utils::truncate_coordinate($_) } tile_to_latlon( $x, $y-1, $z ) ], - south => [ map { Utils::truncate_coordinate($_) } tile_to_latlon( $x, $y+1, $z ) ], - west => [ map { Utils::truncate_coordinate($_) } tile_to_latlon( $x-1, $y, $z ) ], - east => [ map { Utils::truncate_coordinate($_) } tile_to_latlon( $x+1, $y, $z ) ], - here => [ map { Utils::truncate_coordinate($_) } tile_to_latlon( $x, $y, $z ) ], + north => [ map { Utils::truncate_coordinate($_) } $self->tile_to_latlon( $x, $y-1, $z ) ], + south => [ map { Utils::truncate_coordinate($_) } $self->tile_to_latlon( $x, $y+1, $z ) ], + west => [ map { Utils::truncate_coordinate($_) } $self->tile_to_latlon( $x-1, $y, $z ) ], + east => [ map { Utils::truncate_coordinate($_) } $self->tile_to_latlon( $x+1, $y, $z ) ], + here => [ map { Utils::truncate_coordinate($_) } $self->tile_to_latlon( $x, $y, $z ) ], }; } # Given a lat/lon, convert it to OSM tile co-ordinates (precise). -sub latlon_to_tile($$$) { - my ($lat, $lon, $zoom) = @_; +sub latlon_to_tile($$$$) { + my ($self, $lat, $lon, $zoom) = @_; my $x_tile = ($lon + 180) / 360 * 2**$zoom; my $y_tile = (1 - log(tan(deg2rad($lat)) + sec(deg2rad($lat))) / pi) / 2 * 2**$zoom; return ( $x_tile, $y_tile ); @@ -127,9 +127,9 @@ sub latlon_to_tile($$$) { # 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) = latlon_to_tile($lat, $lon, $zoom); +sub latlon_to_tile_with_adjust($$$$) { + my ($self, $lat, $lon, $zoom) = @_; + my ($x_tile, $y_tile) = $self->latlon_to_tile($lat, $lon, $zoom); # Try and have point near centre of map if ($x_tile - int($x_tile) > 0.5) { @@ -143,7 +143,7 @@ sub latlon_to_tile_with_adjust($$$) { } sub tile_to_latlon { - my ($x, $y, $zoom) = @_; + my ($self, $x, $y, $zoom) = @_; my $n = 2 ** $zoom; my $lon = $x / $n * 360 - 180; my $lat = rad2deg(atan(sinh(pi * (1 - 2 * $y / $n)))); @@ -151,9 +151,9 @@ sub tile_to_latlon { } # 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); +sub latlon_to_px($$$$$$) { + my ($self, $lat, $lon, $x_tile, $y_tile, $zoom) = @_; + my ($pin_x_tile, $pin_y_tile) = $self->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); @@ -182,8 +182,8 @@ 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->get_param('zoom') ? $c->get_param('zoom') : 3); - my ($lat, $lon) = tile_to_latlon($tile_x, $tile_y, $zoom); + my $zoom = $self->MIN_ZOOM_LEVEL + (defined $c->get_param('zoom') ? $c->get_param('zoom') : 3); + my ($lat, $lon) = $self->tile_to_latlon($tile_x, $tile_y, $zoom); return ( $lat, $lon ); } diff --git a/perllib/FixMyStreet/Map/UKCouncilWMTS.pm b/perllib/FixMyStreet/Map/UKCouncilWMTS.pm new file mode 100644 index 000000000..53b6859bf --- /dev/null +++ b/perllib/FixMyStreet/Map/UKCouncilWMTS.pm @@ -0,0 +1,55 @@ +package FixMyStreet::Map::UKCouncilWMTS; +use base 'FixMyStreet::Map::WMTSBase'; + +use strict; + +sub zoom_parameters { + my $self = shift; + my $params = { + zoom_levels => scalar $self->scales, + default_zoom => $self->default_zoom, + min_zoom_level => 0, + id_offset => 0, + }; + return $params; +} + +sub tile_parameters { + my $self = shift; + my $params = { + urls => $self->urls, + layer_names => $self->layer_names, + wmts_version => '1.0.0', + layer_style => 'default', + matrix_set => 'default028mm', + suffix => '.png', # appended to tile URLs + size => 256, # pixels + dpi => 96, + inches_per_unit => 39.37, # BNG uses metres + projection => 'EPSG:27700', + origin_x => -5220400.0, + origin_y => 4470200.0, + }; + return $params; +} + +# Reproject a WGS84 lat/lon into BNG easting/northing +sub reproject_from_latlon($$$) { + my ($self, $lat, $lon) = @_; + # do not try to reproject if we have no co-ordindates as convert breaks + return (0.0, 0.0) if $lat == 0 && $lon == 0; + my ($x, $y) = Utils::convert_latlon_to_en($lat, $lon); + return ($x, $y); +} + +# Reproject a BNG easting/northing into WGS84 lat/lon +sub reproject_to_latlon($$$) { + my ($self, $x, $y) = @_; + return (0,0) if $x<0 || $y<0; + my ($lat, $lon) = Utils::convert_en_to_latlon($x, $y); + return ($lat, $lon); +} + +sub map_template { 'wmts' } + +1; diff --git a/perllib/FixMyStreet/Map/WMSBase.pm b/perllib/FixMyStreet/Map/WMSBase.pm new file mode 100644 index 000000000..ce8b6ab38 --- /dev/null +++ b/perllib/FixMyStreet/Map/WMSBase.pm @@ -0,0 +1,151 @@ +# FixMyStreet:Map::WMSBase +# Makes it easier for cobrands to use their own WMS base map. +# This cannot be used directly; you must subclass it and implement several +# methods. See, e.g. FixMyStreet::Map::Northamptonshire. + +package FixMyStreet::Map::WMSBase; +use parent FixMyStreet::Map::WMXBase; + +use strict; + +# A hash of parameters used in calculations for map tiles +sub tile_parameters { + my $params = { + urls => [ '' ], # URL of the map tiles, up to the /{z}/{x}/{y} part + layer_names => [ '' ], + wms_version => '1.0.0', + size => 256, # pixels + dpi => 96, + inches_per_unit => 0, # See OpenLayers.INCHES_PER_UNIT for some options. + projection => 'EPSG:3857', # Passed through to OpenLayers.Projection + }; + return $params; +} + +# This is used to determine which template to render the map with +sub map_template { 'wms' } + +sub get_res { + my ($self, $zoom) = @_; + + my @scales = $self->scales; + + my $res = $scales[$zoom] / + ($self->tile_parameters->{inches_per_unit} * $self->tile_parameters->{dpi}); + + return $res; +} + +sub _get_tile_size { + my ($self, $params) = @_; + + my $res = $self->get_res($params->{zoom}); + return $res * $self->tile_parameters->{size}; +} + +sub _get_tile_params { + my ($self, $params, $left_col, $top_row, $z, $tile_url, $size) = @_; + + my ($min_x, $min_y, $max_x, $max_y) = ($left_col, $top_row - $size, $left_col + $size, $top_row); + + return ($tile_url, $min_x, $min_y, $max_x, $max_y); +} + +sub _get_tile_src { + my ($self, $tile_url, $min_x, $min_y, $max_x, $max_y, $col, $row) = @_; + + my $src = sprintf( '%s&bbox=%d,%d,%d,%d', + $tile_url, $min_x + $col, $min_y - $row, $max_x + $col, $max_y - $row); + + return $src; +} + +sub _get_tile_id { + my ($self, $tile_url, $min_x, $min_y, $max_x, $max_y, $col, $row) = @_; + + return sprintf( '%d.%d', ($min_x + $col), ($min_y - $row) ); +} + +sub _get_row { + my ($self, $top_row, $row_offset, $size) = @_; + return $row_offset * $size; +} + +sub _get_col { + my ($self, $left_col, $col_offset, $size) = @_; + return $col_offset * $size; +} + +sub map_type { 'OpenLayers.Layer.WMS' } + +sub _map_hash_extras { + my $self = shift; + + return { + wms_version => $self->tile_parameters->{wms_version}, + format => $self->tile_parameters->{format}, + }; +} + +sub tile_base_url { + my $self = shift; + my $params = $self->tile_parameters; + return sprintf '%s?version=%s&format=%s&size=%s&width=%s&height=%s&service=WMS&layers=%s&request=GetMap&srs=%s', + $params->{urls}[0], $params->{wms_version}, $params->{format}, $params->{size}, $params->{size}, + $params->{size}, $params->{layer_names}[0], $params->{projection}; +} + +# Given a lat/lon, convert it to 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 ($self, $lat, $lon, $zoom, $rows, $cols) = @_; + my ($x_tile, $y_tile) + = $self->reproject_from_latlon($lat, $lon, $zoom); + + my $tile_params = $self->tile_parameters; + my $res = $self->get_res($zoom); + + $x_tile = $x_tile - ($res * $tile_params->{size}); + $y_tile = $y_tile + ($res * $tile_params->{size}); + + return ( int($x_tile), int($y_tile) ); +} + +sub tile_to_latlon { + my ($self, $fx, $fy, $zoom) = @_; + my ($lat, $lon) = $self->reproject_to_latlon($fx, $fy); + + 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 ($self, $lat, $lon, $x_tile, $y_tile, $zoom) = @_; + my ($pin_x_tile, $pin_y_tile) = $self->reproject_from_latlon($lat, $lon, $zoom); + my $res = $self->get_res($zoom); + my $pin_x = ( $pin_x_tile - $x_tile ) / $res; + my $pin_y = ( $y_tile - $pin_y_tile ) / $res; + return ($pin_x, $pin_y); +} + +sub click_to_tile { + my ($self, $pin_tile, $pin, $zoom, $reverse) = @_; + my $tile_params = $self->tile_parameters; + my $size = $tile_params->{size}; + my $res = $self->get_res($zoom); + + return $reverse ? $pin_tile + ( ( $size - $pin ) * $res ) : $pin_tile + ( $pin * $res ); +} + +# Given some click co-ords (the tile they were on, and where in the +# tile they were), convert to WGS84 and return. +sub click_to_wgs84 { + my ($self, $c, $pin_tile_x, $pin_x, $pin_tile_y, $pin_y) = @_; + my $zoom = (defined $c->get_param('zoom') ? $c->get_param('zoom') : $self->zoom_parameters->{default_zoom}); + my $tile_x = $self->click_to_tile($pin_tile_x, $pin_x, $zoom); + my $tile_y = $self->click_to_tile($pin_tile_y, $pin_y, $zoom, 1); + my ($lat, $lon) = $self->tile_to_latlon($tile_x, $tile_y, $zoom); + return ( $lat, $lon ); +} + +1; diff --git a/perllib/FixMyStreet/Map/WMTSBase.pm b/perllib/FixMyStreet/Map/WMTSBase.pm index 051f8f369..e482b3f37 100644 --- a/perllib/FixMyStreet/Map/WMTSBase.pm +++ b/perllib/FixMyStreet/Map/WMTSBase.pm @@ -4,40 +4,9 @@ # methods. See, e.g. FixMyStreet::Map::Zurich or FixMyStreet::Map::Bristol. package FixMyStreet::Map::WMTSBase; +use parent FixMyStreet::Map::WMXBase; use strict; -use Math::Trig; -use Utils; -use JSON::MaybeXS; - -sub scales { - my $self = shift; - my @scales = ( - # A list of scales corresponding to zoom levels, e.g. - # '192000', - # '96000', - # '48000', - # etc... - ); - return @scales; -} - -# The copyright string to display in the corner of the map. -sub copyright { - return ''; -} - -# A hash of parameters that control the zoom options for the map -sub zoom_parameters { - my $self = shift; - my $params = { - zoom_levels => scalar $self->scales, - default_zoom => 0, - min_zoom_level => 0, - id_offset => 0, - }; - return $params; -} # A hash of parameters used in calculations for map tiles sub tile_parameters { @@ -58,162 +27,46 @@ sub tile_parameters { return $params; } -# This is used to determine which template to render the map with -sub map_template { 'fms' } +sub _get_tile_params { + my ($self, $params, $left_col, $top_row, $z, $tile_url) = @_; -# Reproject a WGS84 lat/lon into an x/y coordinate in this map's CRS. -# Subclasses will want to override this. -sub reproject_from_latlon($$$) { - my ($self, $lat, $lon) = @_; - return (0.0, 0.0); + return ($tile_url, $z, $self->tile_parameters->{suffix}); } -# Reproject a x/y coordinate from this map's CRS into WGS84 lat/lon -# Subclasses will want to override this. -sub reproject_to_latlon($$$) { - my ($self, $x, $y) = @_; - return (0.0, 0.0); +sub _get_tile_src { + my ($self, $tile_url, $z, $suffix, $col, $row) = @_; + + return sprintf( '%s/%d/%d/%d%s', + $tile_url, $z, $row, $col, $suffix); } +sub _get_tile_id { + my ($self, $tile_url, $x, $suffix, $col, $row) = @_; -sub map_tiles { - my ($self, %params) = @_; - my ($left_col, $top_row, $z) = @params{'x_left_tile', 'y_top_tile', 'matrix_id'}; - my $tile_url = $self->tile_base_url; - my $tile_suffix = $self->tile_parameters->{suffix}; - my $cols = $params{cols}; - my $rows = $params{rows}; - - my @col_offsets = (0.. ($cols-1) ); - my @row_offsets = (0.. ($rows-1) ); - - return [ - map { - my $row_offset = $_; - [ - map { - my $col_offset = $_; - my $row = $top_row + $row_offset; - my $col = $left_col + $col_offset; - my $src = sprintf '%s/%d/%d/%d%s', - $tile_url, $z, $row, $col, $tile_suffix; - my $dotted_id = sprintf '%d.%d', $col, $row; - - # return the data structure for the cell - +{ - src => $src, - row_offset => $row_offset, - col_offset => $col_offset, - dotted_id => $dotted_id, - alt => "Map tile $dotted_id", # TODO "NW map tile"? - } - } - @col_offsets - ] - } - @row_offsets - ]; + return sprintf( '%d.%d', $col, $row); } -# 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) = @_; - - # Map centre may be overridden in the query string - $params{latitude} = Utils::truncate_coordinate($c->get_param('lat') + 0) - if defined $c->get_param('lat'); - $params{longitude} = Utils::truncate_coordinate($c->get_param('lon') + 0) - if defined $c->get_param('lon'); - - $params{rows} //= 2; # 2x2 square is default - $params{cols} //= 2; - - my $zoom_params = $self->zoom_parameters; - - $params{zoom} = do { - my $zoom = defined $c->get_param('zoom') - ? $c->get_param('zoom') + 0 - : $c->stash->{page} eq 'report' - ? $zoom_params->{default_zoom}+1 - : $zoom_params->{default_zoom}; - $zoom = $zoom_params->{zoom_levels} - 1 - if $zoom >= $zoom_params->{zoom_levels}; - $zoom = 0 if $zoom < 0; - $zoom; - }; +sub _get_row { + my ($self, $top_row, $row_offset, $size) = @_; + return $top_row + $row_offset; +} - $c->stash->{map} = $self->get_map_hash( %params ); - - if ($params{print_report}) { - $params{zoom}++ unless $params{zoom} >= $zoom_params->{zoom_levels}; - $c->stash->{print_report_map} - = $self->get_map_hash( - %params, - img_type => 'img', - cols => 4, rows => 4, - ); - # NB: we can passthrough img_type as literal here, as only designed for print - - # NB we can do arbitrary size, including non-squares, however we'd have - # to modify .square-map style with padding-bottom percentage calculated in - # an inline style: - # <zarino> in which case, the only change that'd be required is - # removing { padding-bottom: 100% } from .square-map__outer, putting - # the percentage into an inline style on the element itself, and then - # probably renaming .square-map__* to .fixed-aspect-map__* or something - # since it's no longer necessarily square - } +sub _get_col { + my ($self, $left_col, $col_offset, $size) = @_; + return $left_col + $col_offset; } -sub get_map_hash { - my ($self, %params) = @_; - - @params{'x_centre_tile', 'y_centre_tile', 'matrix_id'} - = $self->latlon_to_tile_with_adjust( - @params{'latitude', 'longitude', 'zoom', 'rows', 'cols'}); - - # centre_(row|col) is either in middle, or just to right. - # e.g. if centre is the number in parens: - # 1 (2) 3 => 2 - int( 3/2 ) = 1 - # 1 2 (3) 4 => 3 - int( 4/2 ) = 1 - $params{x_left_tile} = $params{x_centre_tile} - int($params{cols} / 2); - $params{y_top_tile} = $params{y_centre_tile} - int($params{rows} / 2); - - $params{pins} = [ - map { - my $pin = { %$_ }; # shallow clone - ($pin->{px}, $pin->{py}) - = $self->latlon_to_px($pin->{latitude}, $pin->{longitude}, - @params{'x_left_tile', 'y_top_tile', 'zoom'}); - $pin; - } @{ $params{pins} } - ]; +sub map_type { 'OpenLayers.Layer.WMTS' } + +sub _map_hash_extras { + my $self = shift; - my @scales = $self->scales; return { - %params, - type => $self->map_template, - map_type => 'OpenLayers.Layer.WMTS', - tiles => $self->map_tiles( %params ), - copyright => $self->copyright(), - zoom => $params{zoom}, - zoomOffset => $self->zoom_parameters->{min_zoom_level}, - numZoomLevels => $self->zoom_parameters->{zoom_levels}, - tile_size => $self->tile_parameters->{size}, - tile_dpi => $self->tile_parameters->{dpi}, - tile_urls => encode_json( $self->tile_parameters->{urls} ), - tile_suffix => $self->tile_parameters->{suffix}, - layer_names => encode_json( $self->tile_parameters->{layer_names} ), layer_style => $self->tile_parameters->{layer_style}, matrix_set => $self->tile_parameters->{matrix_set}, - map_projection => $self->tile_parameters->{projection}, origin_x => force_float_format($self->tile_parameters->{origin_x}), origin_y => force_float_format($self->tile_parameters->{origin_y}), - scales => encode_json( \@scales ), + tile_suffix => $self->tile_parameters->{suffix}, }; } diff --git a/perllib/FixMyStreet/Map/WMXBase.pm b/perllib/FixMyStreet/Map/WMXBase.pm new file mode 100644 index 000000000..bc529817e --- /dev/null +++ b/perllib/FixMyStreet/Map/WMXBase.pm @@ -0,0 +1,199 @@ +# FixMyStreet:Map::WMXBase +# Common methods for WMS and WMTS maps + +package FixMyStreet::Map::WMXBase; + +use strict; +use Math::Trig; +use Utils; +use JSON::MaybeXS; + +sub scales { + my $self = shift; + my @scales = ( + # A list of scales corresponding to zoom levels, e.g. + # '192000', + # '96000', + # '48000', + # etc... + ); + return @scales; +} + +# The copyright string to display in the corner of the map. +sub copyright { + return ''; +} + +# A hash of parameters that control the zoom options for the map +sub zoom_parameters { + my $self = shift; + my $params = { + zoom_levels => scalar $self->scales, + default_zoom => 0, + min_zoom_level => 0, + id_offset => 0, + }; + return $params; +} + +# This is used to determine which template to render the map with +sub map_template { 'fms' } + +# Reproject a WGS84 lat/lon into an x/y coordinate in this map's CRS. +# Subclasses will want to override this. +sub reproject_from_latlon($$$) { + my ($self, $lat, $lon) = @_; + return (0.0, 0.0); +} + +# Reproject a x/y coordinate from this map's CRS into WGS84 lat/lon +# Subclasses will want to override this. +sub reproject_to_latlon($$$) { + my ($self, $x, $y) = @_; + return (0.0, 0.0); +} + +sub _get_tile_size { + return shift->tile_parameters->{size}; +} + +sub map_tiles { + my ($self, %params) = @_; + my ($left_col, $top_row, $z) = @params{'x_left_tile', 'y_top_tile', 'matrix_id'}; + my $tile_url = $self->tile_base_url; + my $cols = $params{cols}; + my $rows = $params{rows}; + + my @col_offsets = (0.. ($cols-1) ); + my @row_offsets = (0.. ($rows-1) ); + + my $size = $self->_get_tile_size(\%params); + my @params = $self->_get_tile_params(\%params, $left_col, $top_row, $z, $tile_url, $size); + + return [ + map { + my $row_offset = $_; + [ + map { + my $col_offset = $_; + my $row = $self->_get_row($top_row, $row_offset, $size); + my $col = $self->_get_col($left_col, $col_offset, $size); + my $src = $self->_get_tile_src(@params, $col, $row); + my $dotted_id = $self->_get_tile_id(@params, $col, $row); + + # return the data structure for the cell + +{ + src => $src, + row_offset => $row_offset, + col_offset => $col_offset, + dotted_id => $dotted_id, + alt => "Map tile $dotted_id", + } + } + @col_offsets + ] + } + @row_offsets + ]; +} + +# 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) = @_; + + # Map centre may be overridden in the query string + $params{latitude} = Utils::truncate_coordinate($c->get_param('lat') + 0) + if defined $c->get_param('lat'); + $params{longitude} = Utils::truncate_coordinate($c->get_param('lon') + 0) + if defined $c->get_param('lon'); + + $params{rows} //= 2; # 2x2 square is default + $params{cols} //= 2; + + my $zoom_params = $self->zoom_parameters; + + $params{zoom} = do { + my $zoom = defined $c->get_param('zoom') + ? $c->get_param('zoom') + 0 + : $c->stash->{page} eq 'report' + ? $zoom_params->{default_zoom}+1 + : $zoom_params->{default_zoom}; + $zoom = $zoom_params->{zoom_levels} - 1 + if $zoom >= $zoom_params->{zoom_levels}; + $zoom = 0 if $zoom < 0; + $zoom; + }; + + $c->stash->{map} = $self->get_map_hash( %params ); + + if ($params{print_report}) { + $params{zoom}++ unless $params{zoom} >= $zoom_params->{zoom_levels}; + $c->stash->{print_report_map} + = $self->get_map_hash( + %params, + img_type => 'img', + cols => 4, rows => 4, + ); + } +} + +sub _map_hash_extras { return {} } + +sub get_map_hash { + my ($self, %params) = @_; + + @params{'x_centre_tile', 'y_centre_tile', 'matrix_id'} + = $self->latlon_to_tile_with_adjust( + @params{'latitude', 'longitude', 'zoom', 'rows', 'cols'}); + + $params{x_left_tile} = $params{x_centre_tile} - int($params{cols} / 2); + $params{y_top_tile} = $params{y_centre_tile} - int($params{rows} / 2); + + $params{pins} = [ + map { + my $pin = { %$_ }; # shallow clone + ($pin->{px}, $pin->{py}) + = $self->latlon_to_px($pin->{latitude}, $pin->{longitude}, + @params{'x_left_tile', 'y_top_tile', 'zoom'}); + $pin; + } @{ $params{pins} } + ]; + + my @scales = $self->scales; + return { + %params, + type => $self->map_template, + map_type => $self->map_type, + tiles => $self->map_tiles( %params ), + copyright => $self->copyright(), + zoom => $params{zoom}, + zoomOffset => $self->zoom_parameters->{min_zoom_level}, + numZoomLevels => $self->zoom_parameters->{zoom_levels}, + tile_size => $self->tile_parameters->{size}, + tile_dpi => $self->tile_parameters->{dpi}, + tile_urls => encode_json( $self->tile_parameters->{urls} ), + layer_names => encode_json( $self->tile_parameters->{layer_names} ), + map_projection => $self->tile_parameters->{projection}, + scales => encode_json( \@scales ), + compass => $self->compass( $params{x_centre_tile}, $params{y_centre_tile}, $params{zoom} ), + %{ $self->_map_hash_extras }, + }; +} + +sub compass { + my ( $self, $x, $y, $z ) = @_; + return { + north => [ map { Utils::truncate_coordinate($_) } $self->tile_to_latlon( $x, $y-1, $z ) ], + south => [ map { Utils::truncate_coordinate($_) } $self->tile_to_latlon( $x, $y+1, $z ) ], + west => [ map { Utils::truncate_coordinate($_) } $self->tile_to_latlon( $x-1, $y, $z ) ], + east => [ map { Utils::truncate_coordinate($_) } $self->tile_to_latlon( $x+1, $y, $z ) ], + here => [ map { Utils::truncate_coordinate($_) } $self->tile_to_latlon( $x, $y, $z ) ], + }; +} + +1; |