aboutsummaryrefslogtreecommitdiffstats
path: root/perllib
diff options
context:
space:
mode:
Diffstat (limited to 'perllib')
-rw-r--r--perllib/FixMyStreet/Cobrand/Bristol.pm58
-rw-r--r--perllib/FixMyStreet/Map/Bristol.pm77
-rw-r--r--perllib/FixMyStreet/Map/WMTSBase.pm339
-rw-r--r--perllib/Open311/PopulateServiceList.pm7
4 files changed, 478 insertions, 3 deletions
diff --git a/perllib/FixMyStreet/Cobrand/Bristol.pm b/perllib/FixMyStreet/Cobrand/Bristol.pm
new file mode 100644
index 000000000..a6bab287a
--- /dev/null
+++ b/perllib/FixMyStreet/Cobrand/Bristol.pm
@@ -0,0 +1,58 @@
+package FixMyStreet::Cobrand::Bristol;
+use parent 'FixMyStreet::Cobrand::UKCouncils';
+
+use strict;
+use warnings;
+
+sub council_id { return 2561; }
+sub council_area { return 'Bristol'; }
+sub council_name { return 'Bristol County Council'; }
+sub council_url { return 'bristol'; }
+
+sub base_url {
+ my $self = shift;
+ return $self->next::method() if FixMyStreet->config('STAGING_SITE');
+ return 'https://fixmystreet.bristol.gov.uk';
+}
+
+sub example_places {
+ return ( 'BS1 5TR', "Broad Quay" );
+}
+
+sub map_type {
+ 'Bristol';
+}
+
+sub disambiguate_location {
+ my $self = shift;
+ my $string = shift;
+
+ my $town = 'Bristol';
+
+ return {
+ %{ $self->SUPER::disambiguate_location() },
+ town => $town,
+ centre => '51.4526044866206,-2.7706173308649',
+ span => '0.202810508012753,0.60740886659825',
+ bounds => [ 51.3415749466466, -3.11785543094126, 51.5443854546593, -2.51044656434301 ],
+ };
+}
+
+sub pin_colour {
+ my ( $self, $p, $context ) = @_;
+ return 'grey' if $p->state eq 'not responsible';
+ return 'green' if $p->is_fixed || $p->is_closed;
+ return 'red' if $p->state eq 'confirmed';
+ return 'yellow';
+}
+
+sub contact_email {
+ my $self = shift;
+ return join( '@', 'customer.services', 'bristol.gov.uk' );
+}
+
+sub send_questionnaires {
+ return 0;
+}
+
+1;
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 '© 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/perllib/Open311/PopulateServiceList.pm b/perllib/Open311/PopulateServiceList.pm
index b0ffe3806..949e2baa9 100644
--- a/perllib/Open311/PopulateServiceList.pm
+++ b/perllib/Open311/PopulateServiceList.pm
@@ -286,9 +286,10 @@ sub _delete_contacts_not_in_service_list {
}
);
- # for Warwickshire, which is mixed Open311 and email, don't delete the email
- # addresses
- if ($self->_current_body->name eq 'Warwickshire County Council') {
+ # for Warwickshire/Bristol, which are mixed Open311 and email, don't delete
+ # the email addresses
+ if ($self->_current_body->name eq 'Warwickshire County Council' ||
+ $self->_current_body->name eq 'Bristol City Council') {
$found_contacts = $found_contacts->search(
{
email => { -not_like => '%@%' }