aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDave Arter <davea@mysociety.org>2016-05-05 13:01:28 +0100
committerDave Arter <davea@mysociety.org>2016-06-17 16:19:54 +0100
commit0ab232710d71c3bb3dda884d8a49db2965cb9b8a (patch)
treef626aa9facd6b8bcd8d34164af4027bf743531c5
parentec260ce978e68c939648f0a492966ef8e1e83f46 (diff)
[Bristol] Use BCC's base map tiles
This refactors the Map::Zurich module into something that makes it easier to use custom map tiles for a cobrand. This also includes some refactoring of the JS & map templates to make it easier to use a custom WMTS base map in future cobrands.
-rw-r--r--perllib/FixMyStreet/Cobrand/Bristol.pm4
-rw-r--r--perllib/FixMyStreet/Map/Bristol.pm77
-rw-r--r--perllib/FixMyStreet/Map/WMTSBase.pm339
-rw-r--r--templates/web/bristol/maps/fms.html13
-rw-r--r--templates/web/bristol/maps/noscript_map.html70
-rw-r--r--templates/web/bristol/maps/openlayers.html49
-rw-r--r--web/cobrands/bristol/base.scss4
-rw-r--r--web/js/map-wmts-bristol.js176
8 files changed, 732 insertions, 0 deletions
diff --git a/perllib/FixMyStreet/Cobrand/Bristol.pm b/perllib/FixMyStreet/Cobrand/Bristol.pm
index ad4fe75f3..a6bab287a 100644
--- a/perllib/FixMyStreet/Cobrand/Bristol.pm
+++ b/perllib/FixMyStreet/Cobrand/Bristol.pm
@@ -19,6 +19,10 @@ sub example_places {
return ( 'BS1 5TR', "Broad Quay" );
}
+sub map_type {
+ 'Bristol';
+}
+
sub disambiguate_location {
my $self = shift;
my $string = shift;
diff --git a/perllib/FixMyStreet/Map/Bristol.pm b/perllib/FixMyStreet/Map/Bristol.pm
new file mode 100644
index 000000000..7098ceb40
--- /dev/null
+++ b/perllib/FixMyStreet/Map/Bristol.pm
@@ -0,0 +1,77 @@
+# FixMyStreet:Map::Bristol
+# Bristol use their own tiles on their cobrand
+
+package FixMyStreet::Map::Bristol;
+use base 'FixMyStreet::Map::WMTSBase';
+
+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 tile_parameters {
+ my $self = shift;
+ my $params = {
+ url => 'https://maps.bristol.gov.uk/arcgis/rest/services/base/2015_BCC_96dpi/MapServer/WMTS/tile',
+ wmts_version => '1.0.0',
+ layer_name => '2015_BCC_96dpi',
+ 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',
+ # The original tile origin values from the getCapabilities call are
+ # -5220400.0/4470200.0, but this results in the map tile being offset
+ # slightly. These corrected values were figured out manually by
+ # trial and error...
+ origin_x => -5220385.5,
+ origin_y => 4470189.0,
+ };
+ return $params;
+}
+
+sub scales {
+ my $self = shift;
+ my @scales = (
+ '192000', # resolution: 50.800101600203206
+ '96000', # resolution: 25.400050800101603
+ '48000', # resolution: 12.700025400050801
+ '24000', # resolution: 6.350012700025401
+ '12000', # resolution: 3.1750063500127004
+ '6000', # resolution: 1.5875031750063502
+ '3000', # resolution: 0.7937515875031751
+ '1250', # resolution: 0.33072982812632296
+ '750', # resolution: 0.19843789687579377
+ );
+ return @scales;
+}
+
+sub copyright {
+ return '&copy; BCC';
+}
+
+# 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/WMTSBase.pm b/perllib/FixMyStreet/Map/WMTSBase.pm
new file mode 100644
index 000000000..13b6d8091
--- /dev/null
+++ b/perllib/FixMyStreet/Map/WMTSBase.pm
@@ -0,0 +1,339 @@
+# FixMyStreet:Map::WMTSBase
+# Makes it easier for cobrands to use their own WMTS base map.
+# This cannot be used directly; you must subclass it and implement several
+# methods. See, e.g. FixMyStreet::Map::Zurich or FixMyStreet::Map::Bristol.
+
+package FixMyStreet::Map::WMTSBase;
+
+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 {
+ my $params = {
+ url => '', # URL of the map tiles, up to the /{z}/{x}/{y} part
+ wmts_version => '1.0.0',
+ layer_style => '',
+ matrix_set => '',
+ suffix => '', # appended to tile URLs
+ size => 256, # pixels
+ dpi => 96,
+ inches_per_unit => 0, # See OpenLayers.INCHES_PER_UNIT for some options.
+ origin_x => 0,
+ origin_y => 0,
+ projection => 'EPSG:3857', # Passed through to OpenLayers.Projection
+ };
+ return $params;
+}
+
+# This is used to determine which template to render the map with
+sub map_type {
+ return '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 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
+ ];
+}
+
+# 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,
+ );
+ # 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_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} }
+ ];
+
+ my @scales = $self->scales;
+ return {
+ %params,
+ type => $self->map_type,
+ 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->{default_zoom},
+ tile_size => $self->tile_parameters->{size},
+ tile_dpi => $self->tile_parameters->{dpi},
+ tile_url => $self->tile_parameters->{url},
+ tile_suffix => $self->tile_parameters->{suffix},
+ layer_name => $self->tile_parameters->{layer_name},
+ layer_style => $self->tile_parameters->{layer_style},
+ matrix_set => $self->tile_parameters->{matrix_set},
+ map_projection => $self->tile_parameters->{projection},
+ origin_x => $self->tile_parameters->{origin_x},
+ origin_y => $self->tile_parameters->{origin_y},
+ scales => encode_json \@scales,
+ };
+}
+
+sub tile_base_url {
+ my $self = shift;
+ my $params = $self->tile_parameters;
+ return sprintf '%s/%s/%s/%s/%s',
+ $params->{url}, $params->{wmts_version}, $params->{layer_name},
+ $params->{layer_style}, $params->{matrix_set};
+}
+
+# Given a lat/lon, convert it to tile co-ordinates (precise).
+sub latlon_to_tile($$$$) {
+ my ($self, $lat, $lon, $zoom) = @_;
+
+ my ($x, $y) = $self->reproject_from_latlon($lat, $lon);
+
+ my $tile_params = $self->tile_parameters;
+
+ my $matrix_id = $zoom + $self->zoom_parameters->{id_offset};
+ my @scales = $self->scales;
+ my $tileOrigin = {
+ lon => $tile_params->{origin_x},
+ lat => $tile_params->{origin_y}
+ };
+ my $res = $scales[$matrix_id] /
+ ($tile_params->{inches_per_unit} * $tile_params->{dpi});
+ # OpenLayers.INCHES_PER_UNIT[units] * OpenLayers.DOTS_PER_INCH
+
+ my $fx = ( $x - $tileOrigin->{lon} ) / ($res * $tile_params->{size});
+ my $fy = ( $tileOrigin->{lat} - $y ) / ($res * $tile_params->{size});
+
+ 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).
+#
+# Takes parameter for rows/cols. For even sizes (2x2, 4x4 etc.) will
+# do adjustment, but simply returns actual for odd sizes.
+#
+sub latlon_to_tile_with_adjust {
+ my ($self, $lat, $lon, $zoom, $rows, $cols) = @_;
+ my ($x_tile, $y_tile, $matrix_id)
+ = my @ret
+ = $self->latlon_to_tile($lat, $lon, $zoom);
+
+ # Try and have point near centre of map, passing through if odd
+ unless ($cols % 2) {
+ if ($x_tile - int($x_tile) > 0.5) {
+ $x_tile += 1;
+ }
+ }
+ unless ($rows % 2) {
+ 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 ($self, $fx, $fy, $zoom) = @_;
+
+ my $tile_params = $self->tile_parameters;
+ my $matrix_id = $zoom + $self->zoom_parameters->{id_offset};
+ my @scales = $self->scales;
+ my $tileOrigin = {
+ lon => $tile_params->{origin_x},
+ lat => $tile_params->{origin_y}
+ };
+ my $res = $scales[$matrix_id] /
+ ($tile_params->{inches_per_unit} * $tile_params->{dpi});
+ # OpenLayers.INCHES_PER_UNIT[units] * OpenLayers.DOTS_PER_INCH
+
+ my $x = $fx * $res * $tile_params->{size} + $tileOrigin->{lon};
+ my $y = $tileOrigin->{lat} - $fy * $res * $tile_params->{size};
+
+ my ($lat, $lon) = $self->reproject_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 ($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 = $self->tile_to_px($pin_x_tile, $x_tile);
+ my $pin_y = $self->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 ($self, $p, $c) = @_;
+ $p = $self->tile_parameters->{size} * ($p - $c);
+ $p = int($p + .5 * ($p <=> 0));
+ return $p;
+}
+
+sub click_to_tile {
+ my ($self, $pin_tile, $pin) = @_;
+ my $tile_size = $self->tile_parameters->{size};
+ $pin -= $tile_size while $pin > $tile_size;
+ $pin += $tile_size while $pin < 0;
+ return $pin_tile + $pin / $tile_size;
+}
+
+# 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 $tile_x = $self->click_to_tile($pin_tile_x, $pin_x);
+ my $tile_y = $self->click_to_tile($pin_tile_y, $pin_y);
+ my $zoom = (defined $c->get_param('zoom') ? $c->get_param('zoom') : $self->zoom_parameters->{default_zoom});
+ my ($lat, $lon) = $self->tile_to_latlon($tile_x, $tile_y, $zoom);
+ return ( $lat, $lon );
+}
+
+1;
diff --git a/templates/web/bristol/maps/fms.html b/templates/web/bristol/maps/fms.html
new file mode 100644
index 000000000..a1a2a3f7b
--- /dev/null
+++ b/templates/web/bristol/maps/fms.html
@@ -0,0 +1,13 @@
+[% map_js = BLOCK %]
+<!-- <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="[% version('/js/OpenLayers.2.11.zurich.js') %]"></script>
+<script type="text/javascript" src="[% version('/js/OpenLayers.Projection.OrdnanceSurvey.js') %]"></script>
+<script type="text/javascript" src="[% version('/js/map-OpenLayers.js') %]"></script>
+<script type="text/javascript" src="[% version('/js/map-wmts-bristol.js') %]"></script>
+<script type="text/javascript" src="[% version('/js/jquery.ba-hashchange.min.js') %]"></script>
+<!--[if lte IE 6]>
+ <link rel="stylesheet" href="/js/OpenLayers-2.13.1/theme/default/ie6-style.css" type="text/css">
+<![endif]-->
+[% END %]
+
+[% map_html = INCLUDE maps/openlayers.html %]
diff --git a/templates/web/bristol/maps/noscript_map.html b/templates/web/bristol/maps/noscript_map.html
new file mode 100644
index 000000000..5c2a2c064
--- /dev/null
+++ b/templates/web/bristol/maps/noscript_map.html
@@ -0,0 +1,70 @@
+<div class="noscript square-map__outer">
+ <div class="square-map__inner">
+ <div id="[% nsm_prefix %]drag">
+ [%- FOR row IN map.tiles -%]
+ [%- FOR tile IN row -%]
+ [%- top_px = tile.row_offset * map.tile_size -%]
+ [%- left_px = tile.col_offset * map.tile_size %]
+ <[% map.img_type %]
+ class="square-map__tile"
+ alt="[% tile.alt %]"
+ id="[% nsm_prefix %]t[% tile.dotted_id %]"
+ name="tile_[% tile.dotted_id %]"
+ src="[% tile.src %]"
+ style="width: [% 100 / map.cols %]%; height: auto; float: left;">
+ [%- END -%]
+ [% END %]
+ </div>
+ <div id="[% nsm_prefix %]pins">[% FOR pin IN map.pins %][% INCLUDE pin %][% END %]</div>
+ [% INCLUDE compass %]
+ </div>
+</div>
+
+[% BLOCK compass %]
+[%
+ north = c.uri_with( { lat = map.compass.north.0, lon = map.compass.north.1, zoom = map.zoom } )
+ south = c.uri_with( { lat = map.compass.south.0, lon = map.compass.south.1, zoom = map.zoom } )
+ east = c.uri_with( { lat = map.compass.east.0, lon = map.compass.east.1, zoom = map.zoom } )
+ west = c.uri_with( { lat = map.compass.west.0, lon = map.compass.west.1, zoom = map.zoom } )
+ world = c.uri_with( { zoom = 0 } );
+ SET zoom_in = c.uri_with( { lat = map.latitude, lon = map.longitude, zoom = map.zoom + 1 } ) IF map.zoom < map.numZoomLevels - 1;
+ SET zoom_out = c.uri_with( { lat = map.latitude, lon = map.longitude, zoom = map.zoom - 1 } ) IF map.zoom > 0;
+ SET zoom_in = '#' IF map.zoom >= map.numZoomLevels - 1;
+ SET zoom_out = '#' IF map.zoom <= 0;
+%]
+<div style="position: absolute; left: 4px; top: 4px;" class="olControlPanZoom olControlNoSelect" unselectable="on">
+ <div style="position: absolute; left: 13px; top: 4px; width: 18px; height: 18px;"><a rel="nofollow" href="[% north %]"><img style="position: relative; width: 18px; height: 18px;" src="/js/OpenLayers-2.13.1/img/north-mini.png" border="0"></a></div>
+ <div style="position: absolute; left: 4px; top: 22px; width: 18px; height: 18px;"><a rel="nofollow" href="[% west %]"><img style="position: relative; width: 18px; height: 18px;" src="/js/OpenLayers-2.13.1/img/west-mini.png" border="0"></a></div>
+ <div style="position: absolute; left: 22px; top: 22px; width: 18px; height: 18px;"><a rel="nofollow" href="[% east %]"><img style="position: relative; width: 18px; height: 18px;" src="/js/OpenLayers-2.13.1/img/east-mini.png" border="0"></a></div>
+ <div style="position: absolute; left: 13px; top: 40px; width: 18px; height: 18px;"><a rel="nofollow" href="[% south %]"><img style="position: relative; width: 18px; height: 18px;" src="/js/OpenLayers-2.13.1/img/south-mini.png" border="0"></a></div>
+ <div style="position: absolute; left: 13px; top: 63px; width: 18px; height: 18px;"><a rel="nofollow" href="[% zoom_in %]"><img style="position: relative; width: 18px; height: 18px;" src="/js/OpenLayers-2.13.1/img/zoom-plus-mini.png" border="0"></a></div>
+ <div style="position: absolute; left: 13px; top: 81px; width: 18px; height: 18px;"><a rel="nofollow" href="[% world %]"><img style="position: relative; width: 18px; height: 18px;" src="/js/OpenLayers-2.13.1/img/zoom-world-mini.png" border="0"></a></div>
+ <div style="position: absolute; left: 13px; top: 99px; width: 18px; height: 18px;"><a rel="nofollow" href="[% zoom_out %]"><img style="position: relative; width: 18px; height: 18px;" src="/js/OpenLayers-2.13.1/img/zoom-minus-mini.png" border="0"></a></div>
+</div>
+[% END %]
+
+
+[% BLOCK pin %]
+
+[% IF pin.id %]
+<a title="[% pin.title | html %]" href="[% c.uri_for('/report/' _ pin.id) %]">
+[%- END -%]
+<img
+ data-foo="[% pin.px %],[% pin.py %]/ [% map.tile_size %]*[% map.cols %],[% map.rows %]"
+ border="0"
+ class="pin"
+ src="[% c.uri_for( c.cobrand.path_to_pin_icons _ 'pin-' _ pin.colour _ '.png') %]"
+ alt="[% loc('Problem') %]"
+ style="
+ top: [% pin.py / ( map.tile_size * map.rows ) * 100 %]%;
+ left: [% pin.px / ( map.tile_size * map.cols ) * 100 %]%;
+ position: absolute;
+ margin-left: -24px; /* Half of 48px wide image */
+ margin-top: -64px; /* All of 64px tall image */
+ "
+>
+[%- IF pin.id -%]
+</a>
+[% END %]
+
+[% END %]
diff --git a/templates/web/bristol/maps/openlayers.html b/templates/web/bristol/maps/openlayers.html
new file mode 100644
index 000000000..4b178e21a
--- /dev/null
+++ b/templates/web/bristol/maps/openlayers.html
@@ -0,0 +1,49 @@
+[% IF map.clickable %]
+ [% map.img_type = 'input type="image"' %]
+[% ELSE %]
+ [% map.img_type = 'img' %]
+[% END %]
+
+<input type="hidden" name="zoom" value="[% map.zoom %]">
+<script type="text/javascript">
+var fixmystreet = {
+ 'page': '[% page %]',
+ 'area': [ [% map.area.join(',') %] ],
+ 'all_pins': '[% all_pins %]',
+ 'latitude': [% map.latitude %],
+ 'longitude': [% map.longitude %],
+[% IF map.any_zoom -%]
+ 'zoomToBounds': 1,
+[%- END %]
+[% IF map.zoom -%]
+ 'zoom': [% map.zoom %],
+[%- END %]
+ 'pin_prefix': '[% c.cobrand.path_to_pin_icons %]',
+ 'numZoomLevels': [% map.numZoomLevels %],
+ 'zoomOffset': [% map.zoomOffset %],
+ 'map_type': [% map.map_type %],
+ 'pins': [% INCLUDE maps/pins_js.html %],
+
+ 'wmts_config': {
+ 'map_projection': '[% map.map_projection %]',
+ 'tile_dpi': [% map.tile_dpi %],
+ 'tile_url': '[% map.tile_url %]',
+ 'tile_suffix': '[% map.tile_suffix %]',
+ 'layer_name': '[% map.layer_name %]',
+ 'layer_style': '[% map.layer_style %]',
+ 'matrix_set': '[% map.matrix_set %]',
+ 'scales': [% map.scales %],
+ 'origin_x': [% map.origin_x %],
+ 'origin_y': [% map.origin_y %]
+ }
+}
+</script>
+<div id="map_box" aria-hidden="true">
+ [% pre_map %]
+ <div id="map">
+ [% INCLUDE 'maps/noscript_map.html' %]
+ </div>
+ [% IF map.copyright %]
+ <div class="olControlAttribution" style="position: absolute;">[% map.copyright %]</div>
+ [% END %]
+
diff --git a/web/cobrands/bristol/base.scss b/web/cobrands/bristol/base.scss
index b5cb6b85e..746e17064 100644
--- a/web/cobrands/bristol/base.scss
+++ b/web/cobrands/bristol/base.scss
@@ -125,3 +125,7 @@ a#geolocate_link {
label {
@extend %bold-font;
}
+
+#map #drag .square-map__tile {
+ position: static;
+}
diff --git a/web/js/map-wmts-bristol.js b/web/js/map-wmts-bristol.js
new file mode 100644
index 000000000..a1889beed
--- /dev/null
+++ b/web/js/map-wmts-bristol.js
@@ -0,0 +1,176 @@
+/*
+ * Maps for FMS using Bristol City Council's WMTS tile server
+ */
+
+// From the 'fullExtent' key from http://maps.bristol.gov.uk/arcgis/rest/services/base/2015_BCC_96dpi/MapServer?f=pjson
+var layer_bounds = new OpenLayers.Bounds(
+ 268756.31099999975, // W
+ 98527.70309999958, // S
+ 385799.51099999994, // E
+ 202566.10309999995); // N
+
+var matrix_ids = [
+ {
+ "identifier": "0",
+ "supportedCRS": "urn:ogc:def:crs:EPSG::27700",
+ "scaleDenominator": 181428.9342864172,
+ "tileWidth": 256,
+ "tileHeight": 256,
+ "matrixWidth": 432,
+ "matrixHeight": 337
+ },
+ {
+ "identifier": "1",
+ "supportedCRS": "urn:ogc:def:crs:EPSG::27700",
+ "scaleDenominator": 90714.4671432086,
+ "tileWidth": 256,
+ "tileHeight": 256,
+ "matrixWidth": 863,
+ "matrixHeight": 673
+ },
+ {
+ "identifier": "2",
+ "supportedCRS": "urn:ogc:def:crs:EPSG::27700",
+ "scaleDenominator": 45357.2335716043,
+ "tileWidth": 256,
+ "tileHeight": 256,
+ "matrixWidth": 1725,
+ "matrixHeight": 1345
+ },
+ {
+ "identifier": "3",
+ "supportedCRS": "urn:ogc:def:crs:EPSG::27700",
+ "scaleDenominator": 22678.61678580215,
+ "tileWidth": 256,
+ "tileHeight": 256,
+ "matrixWidth": 3449,
+ "matrixHeight": 2690
+ },
+ {
+ "identifier": "4",
+ "supportedCRS": "urn:ogc:def:crs:EPSG::27700",
+ "scaleDenominator": 11339.308392901075,
+ "tileWidth": 256,
+ "tileHeight": 256,
+ "matrixWidth": 6898,
+ "matrixHeight": 5379
+ },
+ {
+ "identifier": "5",
+ "supportedCRS": "urn:ogc:def:crs:EPSG::27700",
+ "scaleDenominator": 5669.654196450538,
+ "tileWidth": 256,
+ "tileHeight": 256,
+ "matrixWidth": 13795,
+ "matrixHeight": 10758
+ },
+ {
+ "identifier": "6",
+ "supportedCRS": "urn:ogc:def:crs:EPSG::27700",
+ "scaleDenominator": 2834.827098225269,
+ "tileWidth": 256,
+ "tileHeight": 256,
+ "matrixWidth": 27590,
+ "matrixHeight": 21515
+ },
+ {
+ "identifier": "7",
+ "supportedCRS": "urn:ogc:def:crs:EPSG::27700",
+ "scaleDenominator": 1181.177957593862,
+ "tileWidth": 256,
+ "tileHeight": 256,
+ "matrixWidth": 66215,
+ "matrixHeight": 51634
+ },
+ {
+ "identifier": "8",
+ "supportedCRS": "urn:ogc:def:crs:EPSG::27700",
+ "scaleDenominator": 708.7067745563172,
+ "tileWidth": 256,
+ "tileHeight": 256,
+ "matrixWidth": 110359,
+ "matrixHeight": 86057
+ }
+];
+
+/*
+ * set_map_config() is called on dom ready in map-OpenLayers.js
+ * to setup the way the map should operate.
+ */
+ function set_map_config(perm) {
+ // This stuff is copied from js/map-bing-ol.js
+
+ var nav_opts = { zoomWheelEnabled: false };
+ if (fixmystreet.page == 'around' && $('html').hasClass('mobile')) {
+ nav_opts = {};
+ }
+ fixmystreet.nav_control = new OpenLayers.Control.Navigation(nav_opts);
+
+ fixmystreet.controls = [
+ new OpenLayers.Control.ArgParser(),
+ fixmystreet.nav_control
+ ];
+ if ( fixmystreet.page != 'report' || !$('html').hasClass('mobile') ) {
+ fixmystreet.controls.push( new OpenLayers.Control.PanZoomFMS({id: 'fms_pan_zoom' }) );
+ }
+
+ /* Linking back to around from report page, keeping track of map moves */
+ if ( fixmystreet.page == 'report' ) {
+ fixmystreet.controls.push( new OpenLayers.Control.PermalinkFMS('key-tool-problems-nearby', '/around') );
+ }
+
+ fixmystreet.map_type = OpenLayers.Layer.WMTS;
+
+ // Set DPI - default is 72
+ OpenLayers.DOTS_PER_INCH = fixmystreet.wmts_config.tile_dpi;
+
+ fixmystreet.map_options = {
+ maxExtent: layer_bounds,
+ units: 'm',
+ scales: fixmystreet.wmts_config.scales
+ };
+
+ fixmystreet.layer_options = [{
+ projection: new OpenLayers.Projection(fixmystreet.wmts_config.map_projection),
+ name: fixmystreet.wmts_config.layer_name,
+ layer: fixmystreet.wmts_config.layer_name,
+ formatSuffix: fixmystreet.wmts_config.tile_suffix.replace(".", ""),
+ matrixSet: fixmystreet.wmts_config.matrix_set,
+ requestEncoding: "REST",
+ url: fixmystreet.wmts_config.tile_url,
+ style: fixmystreet.wmts_config.layer_style,
+ matrixIds: matrix_ids,
+ tileOrigin: new OpenLayers.LonLat(fixmystreet.wmts_config.origin_x, fixmystreet.wmts_config.origin_y)
+ }];
+
+ // Give main code a new bbox_strategy that translates between
+ // lat/lon and our WMTS layer's coordinates
+ fixmystreet.bbox_strategy = new OpenLayers.Strategy.ReprojectBBOX({ratio: 1});
+}
+
+OpenLayers.Strategy.ReprojectBBOX = OpenLayers.Class(OpenLayers.Strategy.BBOX, {
+ getMapBounds: function() {
+ // Get the map bounds but return them in lat/lon, not
+ // local coordinates
+ if (this.layer.map === null) {
+ return null;
+ }
+
+ var localBounds = this.layer.map.getExtent();
+ // Transform bound corners into WGS84
+ localBounds.transform( new OpenLayers.Projection(fixmystreet.wmts_config.map_projection), new OpenLayers.Projection("EPSG:4326") );
+ return localBounds;
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.ReprojectBBOX"
+});
+
+function fms_marker_size_for_zoom(zoom) {
+ if (zoom >= 7) {
+ return 'normal';
+ } else if (zoom >= 4) {
+ return 'small';
+ } else {
+ return 'mini';
+ }
+}