aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--perllib/FixMyStreet/App/Controller/Reports.pm30
-rw-r--r--perllib/FixMyStreet/Cobrand/Bromley.pm131
-rw-r--r--t/cobrand/bromley.t12
-rw-r--r--templates/web/bromley/about/heatmap-list.html28
-rwxr-xr-xtemplates/web/bromley/about/heatmap.html50
-rw-r--r--templates/web/bromley/footer_extra_js.html5
-rw-r--r--web/cobrands/bromley/HeatmapLayer.js294
-rw-r--r--web/cobrands/bromley/js.js75
-rw-r--r--web/js/map-OpenLayers.js9
9 files changed, 623 insertions, 11 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm
index 85848a181..b77e89d0e 100644
--- a/perllib/FixMyStreet/App/Controller/Reports.pm
+++ b/perllib/FixMyStreet/App/Controller/Reports.pm
@@ -118,7 +118,7 @@ sub ward : Path : Args(2) {
$c->forward('/auth/get_csrf_token');
- my @wards = split /\|/, $ward || "";
+ my @wards = $c->get_param('wards') ? $c->get_param_list('wards', 1) : split /\|/, $ward || "";
$c->forward( 'body_check', [ $body ] );
# If viewing multiple wards, rewrite the url from
@@ -154,7 +154,8 @@ sub ward : Path : Args(2) {
$c->forward( 'load_and_group_problems' );
if ($c->get_param('ajax')) {
- $c->detach('ajax', [ 'reports/_problem-list.html' ]);
+ my $ajax_template = $c->stash->{ajax_template} || 'reports/_problem-list.html';
+ $c->detach('ajax', [ $ajax_template ]);
}
$c->stash->{rss_url} = '/rss/reports/' . $body_short;
@@ -164,14 +165,14 @@ sub ward : Path : Args(2) {
$c->stash->{stats} = $c->cobrand->get_report_stats();
my @categories = $c->stash->{body}->contacts->not_deleted->search( undef, {
- columns => [ 'category', 'extra' ],
+ columns => [ 'id', 'category', 'extra' ],
distinct => 1,
order_by => [ 'category' ],
} )->all;
$c->stash->{filter_categories} = \@categories;
$c->stash->{filter_category} = { map { $_ => 1 } $c->get_param_list('filter_category', 1) };
- my $pins = $c->stash->{pins};
+ my $pins = $c->stash->{pins} || [];
my %map_params = (
latitude => @$pins ? $pins->[0]{latitude} : 0,
@@ -554,7 +555,7 @@ sub load_and_group_problems : Private {
my $states = $c->stash->{filter_problem_states};
my $where = {
- state => [ keys %$states ]
+ 'me.state' => [ keys %$states ]
};
$c->forward('check_non_public_reports_permission', [ $where ] );
@@ -613,12 +614,21 @@ sub load_and_group_problems : Private {
$where->{longitude} = { '>=', $min_lon, '<', $max_lon };
}
- $problems = $problems->search(
- $where,
- $filter
- )->include_comment_counts->page( $page );
+ my $cobrand_problems = $c->cobrand->call_hook('munge_load_and_group_problems', $where, $filter);
- $c->stash->{pager} = $problems->pager;
+ # JS will request the same (or more) data client side
+ return if $c->get_param('js');
+
+ if ($cobrand_problems) {
+ $problems = $cobrand_problems;
+ } else {
+ $problems = $problems->search(
+ $where,
+ $filter
+ )->include_comment_counts->page( $page );
+
+ $c->stash->{pager} = $problems->pager;
+ }
my ( %problems, @pins );
while ( my $problem = $problems->next ) {
diff --git a/perllib/FixMyStreet/Cobrand/Bromley.pm b/perllib/FixMyStreet/Cobrand/Bromley.pm
index 3ae8ef140..55a366b60 100644
--- a/perllib/FixMyStreet/Cobrand/Bromley.pm
+++ b/perllib/FixMyStreet/Cobrand/Bromley.pm
@@ -3,6 +3,7 @@ use parent 'FixMyStreet::Cobrand::UKCouncils';
use strict;
use warnings;
+use utf8;
use DateTime::Format::W3CDTF;
sub council_area_id { return 2482; }
@@ -139,6 +140,39 @@ sub tweak_all_reports_map {
$c->stash->{map}->{any_zoom} = 0;
$c->stash->{map}->{zoom} = 11;
}
+
+ # A place where this can happen
+ return unless $c->stash->{template} && $c->stash->{template} eq 'about/heatmap.html';
+
+ my $children = $c->stash->{body}->first_area_children;
+ foreach (values %$children) {
+ $_->{url} = $c->uri_for( $c->stash->{body_url}
+ . '/' . $c->cobrand->short_name( $_ )
+ );
+ }
+ $c->stash->{children} = $children;
+
+ my %subcats = $self->subcategories;
+ my $filter = $c->stash->{filter_categories};
+ my @new_contacts;
+ foreach (@$filter) {
+ push @new_contacts, $_;
+ foreach (@{$subcats{$_->id}}) {
+ push @new_contacts, {
+ category => $_->{key},
+ category_display => (" " x 4) . $_->{name},
+ };
+ }
+ }
+ $c->stash->{filter_categories} = \@new_contacts;
+
+ if (!%{$c->stash->{filter_category}}) {
+ my $cats = $c->user->categories;
+ my $subcats = $c->user->get_extra_metadata('subcategories') || [];
+ $c->stash->{filter_category} = { map { $_ => 1 } @$cats, @$subcats } if @$cats || @$subcats;
+ }
+
+ $c->stash->{ward_hash} = { map { $_->{id} => 1 } @{$c->stash->{wards}} } if $c->stash->{wards};
}
sub title_list {
@@ -300,5 +334,102 @@ sub add_admin_subcategories {
return \@new_contacts;
}
+sub about_hook {
+ my $self = shift;
+ my $c = $self->{c};
+
+ # Display a special custom dashboard page, with heatmap
+ if ($c->stash->{template} eq 'about/heatmap.html') {
+ $c->forward('/dashboard/check_page_allowed');
+ # We want a special sidebar
+ $c->stash->{ajax_template} = "about/heatmap-list.html";
+ $c->set_param('js', 1) unless $c->get_param('ajax'); # Want to load pins client-side
+ $c->forward('/reports/body', [ 'Bromley' ]);
+ }
+}
+
+# On heatmap page, include querying on subcategories, wards, dates, provided
+sub munge_load_and_group_problems {
+ my ($self, $where, $filter) = @_;
+ my $c = $self->{c};
+
+ return unless $c->stash->{template} && $c->stash->{template} eq 'about/heatmap.html';
+
+ if (!$where->{category}) {
+ my $cats = $c->user->categories;
+ my $subcats = $c->user->get_extra_metadata('subcategories') || [];
+ $where->{category} = [ @$cats, @$subcats ] if @$cats || @$subcats;
+ }
+
+ my %subcats = $self->subcategories;
+ my $subcat;
+ my %chosen = map { $_ => 1 } @{$where->{category} || []};
+ my @subcat = grep { $chosen{$_} } map { $_->{key} } map { @$_ } values %subcats;
+ if (@subcat) {
+ my %chosen = map { $_ => 1 } @subcat;
+ $where->{'-or'} = {
+ category => [ grep { !$chosen{$_} } @{$where->{category}} ],
+ subcategory => \@subcat,
+ };
+ delete $where->{category};
+ }
+
+ # Wards
+ my @areas = @{$c->user->area_ids || []};
+ # Want to get everything if nothing given in an ajax call
+ if (!$c->stash->{wards} && @areas) {
+ $c->stash->{wards} = [ map { { id => $_ } } @areas ];
+ $where->{areas} = [
+ map { { 'like', '%,' . $_ . ',%' } } @areas
+ ];
+ }
+
+ # Date range
+ my $dtf = $c->model('DB')->storage->datetime_parser;
+ my $start_default = DateTime->today(time_zone => FixMyStreet->time_zone || FixMyStreet->local_time_zone)->subtract(months => 3);
+ $c->stash->{start_date} = $c->get_param('start_date') || $start_default->strftime('%Y-%m-%d');
+ $c->stash->{end_date} = $c->get_param('end_date');
+
+ my $start_date = $dtf->parse_datetime($c->stash->{start_date});
+ $where->{'me.confirmed'} = { '>=', $dtf->format_datetime($start_date) };
+ if (my $end_date = $c->stash->{end_date}) {
+ my $one_day = DateTime::Duration->new( days => 1 );
+ $end_date = $dtf->parse_datetime($end_date) + $one_day;
+ $where->{'me.confirmed'} = [ -and => $where->{'me.confirmed'}, { '<', $dtf->format_datetime($end_date) } ];
+ }
+
+ delete $filter->{rows};
+
+ # Load the relevant stuff for the sidebar as well
+ my $problems = $self->problems->search($where, $filter);
+
+ $c->stash->{five_newest} = [ $problems->search(undef, {
+ rows => 5,
+ order_by => { -desc => 'confirmed' },
+ })->all ];
+
+ $c->stash->{ten_oldest} = [ $problems->search({
+ 'me.state' => [ FixMyStreet::DB::Result::Problem->open_states() ],
+ }, {
+ rows => 10,
+ order_by => 'lastupdate',
+ })->all ];
+
+ my $params = { map { my $n = $_; s/me\./problem\./; $_ => $where->{$n} } keys %$where };
+ my @c = $c->model('DB::Comment')->to_body($self->body)->search({
+ %$params,
+ 'me.user_id' => { -not_in => [ $c->user->id, $self->body->comment_user_id ] },
+ 'me.state' => 'confirmed',
+ }, {
+ columns => 'problem_id',
+ group_by => 'problem_id',
+ order_by => { -desc => \'max(me.confirmed)' },
+ rows => 5,
+ })->all;
+ $c->stash->{five_commented} = [ map { $_->problem } @c ];
+
+ return $problems;
+}
+
1;
diff --git a/t/cobrand/bromley.t b/t/cobrand/bromley.t
index 129531fcb..a9f9fb144 100644
--- a/t/cobrand/bromley.t
+++ b/t/cobrand/bromley.t
@@ -228,4 +228,16 @@ subtest 'check special subcategories in admin' => sub {
is_deeply $user->get_extra_metadata('subcategories'), [ 'BLUE' ];
};
+subtest 'check heatmap page' => sub {
+ $user->update({ area_ids => [ 60705 ] });
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'bromley',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $mech->log_in_ok($user->email);
+ $mech->get_ok('/about/heatmap?end_date=2018-12-31');
+ $mech->get_ok('/about/heatmap?filter_category=RED&ajax=1');
+ };
+};
+
done_testing();
diff --git a/templates/web/bromley/about/heatmap-list.html b/templates/web/bromley/about/heatmap-list.html
new file mode 100644
index 000000000..e04df8581
--- /dev/null
+++ b/templates/web/bromley/about/heatmap-list.html
@@ -0,0 +1,28 @@
+<h3>Five newest reports</h3>
+[% INCLUDE column problems = five_newest %]
+
+<h3>Five most recent commented reports<br>
+<small>Not from yourself or Confirm</small>
+</h3>
+[% INCLUDE column problems = five_commented %]
+
+<h3>Ten least recently updated open reports</h3>
+[% INCLUDE column problems = ten_oldest %]
+
+[% BLOCK column %]
+<section class="full-width">
+ <ul class="item-list item-list--reports">
+ [% IF problems.size %]
+ [% FOREACH problem IN problems %]
+ [% INCLUDE 'reports/_list-entry.html' include_sentinfo = 1 include_lastupdate = 1 %]
+ [% END %]
+ [% ELSE %]
+ <li class="item-list__item item-list__item--empty">
+ <p>[% loc('There are no reports to show.') %]</p>
+ </li>
+ [% END %]
+ </ul>
+</section>
+[% END %]
+
+
diff --git a/templates/web/bromley/about/heatmap.html b/templates/web/bromley/about/heatmap.html
new file mode 100755
index 000000000..0cb8bfb1b
--- /dev/null
+++ b/templates/web/bromley/about/heatmap.html
@@ -0,0 +1,50 @@
+[%
+ map_js.push(version('/cobrands/bromley/HeatmapLayer.js'));
+ PROCESS "maps/${map.type}.html";
+ SET bodyclass = 'mappage';
+ INCLUDE 'header.html',
+ title = tprintf(loc('%s - Summary reports'), body.name)
+%]
+
+[% map_html %]
+
+</div>
+<div id="map_sidebar">
+ <div id="side">
+
+ <h1 id="reports_heading">
+ [% body.name %]
+ </h1>
+
+<div class="full-width">
+
+[% INCLUDE "reports/_list-filters.html", use_form_wrapper = 1 %]
+
+<p class="report-list-filters" style='padding-top:0.25em'>
+ From <input type="date" id="start_date" name="start_date" class="form-control" value="[% start_date | html %]">
+ To <input type="date" id="end_date" name="end_date" class="form-control" value="[% end_date | html %]">
+
+<p class="report-list-filters">
+In wards <select class="form-control js-multiple" multiple id="wards" name="wards">
+ [% FOR child IN children.values.sort('name') %]
+ [% SET aid = child.id %]
+ <option value="[% child.name | html %]"[% ' selected' IF ward_hash.$aid %]>[% child.name %]</option>
+ [% END %]
+</select>
+
+</div>
+
+<p class="segmented-control segmented-control--radio">
+ <input type="radio" name="heatmap" id="heatmap_yes" value="Yes">
+ <label class="btn" for="heatmap_yes">Heatmap</label>
+ <input type="radio" name="heatmap" id="heatmap_no" value="No" checked>
+ <label class="btn" for="heatmap_no">Pin map</label>
+</p>
+
+<div id="js-reports-list">
+ [% INCLUDE 'about/heatmap-list.html' %]
+</div>
+
+ </div>
+</div>
+[% INCLUDE 'footer.html' %]
diff --git a/templates/web/bromley/footer_extra_js.html b/templates/web/bromley/footer_extra_js.html
index 3c8711c39..27eb438ca 100644
--- a/templates/web/bromley/footer_extra_js.html
+++ b/templates/web/bromley/footer_extra_js.html
@@ -3,3 +3,8 @@
version('/cobrands/bromley/a-z-nav.js'),
version('/cobrands/fixmystreet-uk-councils/council_validation_rules.js'),
) %]
+[% IF bodyclass.match('mappage');
+ scripts.push(
+ version('/cobrands/bromley/js.js'),
+ );
+END %]
diff --git a/web/cobrands/bromley/HeatmapLayer.js b/web/cobrands/bromley/HeatmapLayer.js
new file mode 100644
index 000000000..93f3e84b2
--- /dev/null
+++ b/web/cobrands/bromley/HeatmapLayer.js
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2010 Bjoern Hoehrmann <http://bjoern.hoehrmann.de/>.
+ * This module is licensed under the same terms as OpenLayers itself.
+ *
+ */
+
+Heatmap = {};
+
+/**
+ * Class: Heatmap.Source
+ */
+Heatmap.Source = OpenLayers.Class({
+
+ /**
+ * APIProperty: lonlat
+ * {OpenLayers.LonLat} location of the heat source
+ */
+ lonlat: null,
+
+ /**
+ * APIProperty: radius
+ * {Number} Heat source radius
+ */
+ radius: null,
+
+ /**
+ * APIProperty: intensity
+ * {Number} Heat source intensity
+ */
+ intensity: null,
+
+ /**
+ * Constructor: Heatmap.Source
+ * Create a heat source.
+ *
+ * Parameters:
+ * lonlat - {OpenLayers.LonLat} Coordinates of the heat source
+ * radius - {Number} Optional radius
+ * intensity - {Number} Optional intensity
+ */
+ initialize: function(lonlat, radius, intensity) {
+ this.lonlat = lonlat;
+ this.radius = radius;
+ this.intensity = intensity;
+ },
+
+ CLASS_NAME: 'Heatmap.Source'
+});
+
+/**
+ * Class: Heatmap.Layer
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+Heatmap.Layer = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Heatmap layer is never a base layer.
+ */
+ isBaseLayer: false,
+
+ /**
+ * Property: points
+ * {Array(<Heatmap.Source>)} internal coordinate list
+ */
+ points: null,
+
+ /**
+ * Property: cache
+ * {Object} Hashtable with CanvasGradient objects
+ */
+ cache: null,
+
+ /**
+ * Property: gradient
+ * {Array(Number)} RGBA gradient map used to colorize the intensity map.
+ */
+ gradient: null,
+
+ /**
+ * Property: canvas
+ * {DOMElement} Canvas element.
+ */
+ canvas: null,
+
+ /**
+ * APIProperty: defaultRadius
+ * {Number} Heat source default radius
+ */
+ defaultRadius: null,
+
+ /**
+ * APIProperty: defaultIntensity
+ * {Number} Heat source default intensity
+ */
+ defaultIntensity: null,
+
+ /**
+ * Constructor: Heatmap.Layer
+ * Create a heatmap layer.
+ *
+ * Parameters:
+ * name - {String} Name of the Layer
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+ this.points = [];
+ this.cache = {};
+ this.canvas = document.createElement('canvas');
+ this.canvas.style.position = 'absolute';
+ this.defaultRadius = 20;
+ this.defaultIntensity = 0.2;
+ this.setGradientStops({
+ 0.00: 0x00d7ff00, // transparent cyan
+ 0.10: 0x00d7ffff, // cyan
+ 0.25: 0x008cffff, // royal blue
+ 0.40: 0x6600ffff, // purple
+ 0.55: 0xca00ffff, // fuschia pink
+ 0.70: 0xff0000ff, // red
+ 0.80: 0xff5b00ff, // orange
+ 0.90: 0xffff00ff, // yellow
+ 1.00: 0xffff00ff // yellow
+ });
+
+ // For some reason OpenLayers.Layer.setOpacity assumes there is
+ // an additional div between the layer's div and its contents.
+ var sub = document.createElement('div');
+ sub.appendChild(this.canvas);
+ this.div.appendChild(sub);
+ },
+
+ /**
+ * APIMethod: setGradientStops
+ * ...
+ *
+ * Parameters:
+ * stops - {Object} Hashtable with stop position as keys and colors
+ * as values. Stop positions are numbers between 0
+ * and 1, color values numbers in 0xRRGGBBAA form.
+ */
+ setGradientStops: function(stops) {
+
+ // There is no need to perform the linear interpolation manually,
+ // it is sufficient to let the canvas implementation do that.
+
+ var ctx = document.createElement('canvas').getContext('2d');
+ var grd = ctx.createLinearGradient(0, 0, 256, 0);
+
+ for (var i in stops) {
+ grd.addColorStop(i, 'rgba(' +
+ ((stops[i] >> 24) & 0xFF) + ',' +
+ ((stops[i] >> 16) & 0xFF) + ',' +
+ ((stops[i] >> 8) & 0xFF) + ',' +
+ ((stops[i] >> 0) & 0xFF) + ')');
+ }
+
+ ctx.fillStyle = grd;
+ ctx.fillRect(0, 0, 256, 1);
+ this.gradient = ctx.getImageData(0, 0, 256, 1).data;
+ },
+
+ /**
+ * APIMethod: addSource
+ * Adds a heat source to the layer.
+ *
+ * Parameters:
+ * source - {<Heatmap.Source>}
+ */
+ addSource: function(source) {
+ this.points.push(source);
+ },
+
+ /**
+ * APIMethod: removeSource
+ * Removes a heat source from the layer.
+ *
+ * Parameters:
+ * source - {<Heatmap.Source>}
+ */
+ removeSource: function(source) {
+ if (this.points && this.points.length) {
+ OpenLayers.Util.removeItem(this.points, source);
+ }
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo: function(bounds, zoomChanged, dragging) {
+
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+ // The code is too slow to update the rendering during dragging.
+ if (dragging)
+ return;
+
+ // Pick some point on the map and use it to determine the offset
+ // between the map's 0,0 coordinate and the layer's 0,0 position.
+ var someLoc = new OpenLayers.LonLat(0,0);
+ var offsetX = this.map.getViewPortPxFromLonLat(someLoc).x -
+ this.map.getLayerPxFromLonLat(someLoc).x;
+ var offsetY = this.map.getViewPortPxFromLonLat(someLoc).y -
+ this.map.getLayerPxFromLonLat(someLoc).y;
+
+ this.canvas.width = this.map.getSize().w;
+ this.canvas.height = this.map.getSize().h;
+
+ var ctx = this.canvas.getContext('2d');
+
+ ctx.save(); // Workaround for a bug in Google Chrome
+ ctx.fillStyle = 'transparent';
+ ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ ctx.restore();
+
+ for (var i in this.points) {
+
+ var src = this.points[i];
+ var rad = src.radius || this.defaultRadius;
+ var int = src.intensity || this.defaultIntensity;
+ var pos = this.map.getLayerPxFromLonLat(src.lonlat);
+ var x = pos.x - rad + offsetX;
+ var y = pos.y - rad + offsetY;
+
+ if (!this.cache[int]) {
+ this.cache[int] = {};
+ }
+
+ if (!this.cache[int][rad]) {
+ var grd = ctx.createRadialGradient(rad, rad, 0, rad, rad, rad);
+ grd.addColorStop(0.0, 'rgba(0, 0, 0, ' + int + ')');
+ grd.addColorStop(1.0, 'transparent');
+ this.cache[int][rad] = grd;
+ }
+
+ ctx.fillStyle = this.cache[int][rad];
+ ctx.translate(x, y);
+ ctx.fillRect(0, 0, 2 * rad, 2 * rad);
+ ctx.translate(-x, -y);
+ }
+
+ var dat = ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
+ var dim = this.canvas.width * this.canvas.height * 4;
+ var pix = dat.data;
+
+ for (var p = 0; p < dim; /* */) {
+ var a = pix[ p + 3 ] * 4;
+ pix[ p++ ] = this.gradient[ a++ ];
+ pix[ p++ ] = this.gradient[ a++ ];
+ pix[ p++ ] = this.gradient[ a++ ];
+ pix[ p++ ] = this.gradient[ a++ ];
+ }
+
+ ctx.putImageData(dat, 0, 0);
+
+ // Unfortunately OpenLayers does not currently support layers that
+ // remain in a fixed position with respect to the screen location
+ // of the base layer, so this puts this layer manually back into
+ // that position using one point's offset as determined earlier.
+ this.canvas.style.left = (-offsetX) + 'px';
+ this.canvas.style.top = (-offsetY) + 'px';
+ },
+
+ /**
+ * APIMethod: getDataExtent
+ * Calculates the max extent which includes all of the heat sources.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getDataExtent: function () {
+ var maxExtent = null;
+
+ if (this.points && (this.points.length > 0)) {
+ maxExtent = new OpenLayers.Bounds();
+ for(var i = 0, len = this.points.length; i < len; ++i) {
+ var point = this.points[i];
+ maxExtent.extend(point.lonlat);
+ }
+ }
+
+ return maxExtent;
+ },
+
+ CLASS_NAME: 'Heatmap.Layer'
+
+});
diff --git a/web/cobrands/bromley/js.js b/web/cobrands/bromley/js.js
new file mode 100644
index 000000000..8ff314189
--- /dev/null
+++ b/web/cobrands/bromley/js.js
@@ -0,0 +1,75 @@
+if (window.Heatmap) {
+ // We do want heatmap page to run on load... Bit cheeky
+ OpenLayers.Strategy.FixMyStreetNoLoad = OpenLayers.Strategy.FixMyStreet;
+ OpenLayers.Strategy.FixMyStreetHeatmap = OpenLayers.Class(OpenLayers.Strategy.FixMyStreet, {
+ // Same as update, but doesn't check layer visibility (as running when markers invisible)
+ update: function(options) {
+ var mapBounds = this.getMapBounds();
+ if (mapBounds !== null && ((options && options.force) ||
+ (this.layer.calculateInRange() && this.invalidBounds(mapBounds)))) {
+ this.calculateBounds(mapBounds);
+ this.resolution = this.layer.map.getResolution();
+ this.triggerRead(options);
+ }
+ },
+ CLASS_NAME: 'OpenLayers.Strategy.FixMyStreetHeatmap'
+ });
+}
+
+fixmystreet.protocol_params.wards = 'wards';
+fixmystreet.protocol_params.start_date = 'start_date';
+fixmystreet.protocol_params.end_date = 'end_date';
+
+$(function(){
+ if (!window.Heatmap) {
+ return;
+ }
+
+ var heat_layer = new Heatmap.Layer("Heatmap");
+ heat_layer.setOpacity(0.7);
+ heat_layer.setVisibility(false);
+
+ var s = new OpenLayers.Strategy.FixMyStreetHeatmap();
+ s.setLayer(heat_layer);
+ s.activate();
+ // Now it's listening on heat layer, set it to update markers layer
+ s.layer = fixmystreet.markers;
+
+ function create_heat_layer() {
+ heat_layer.points = [];
+ for (var i = 0; i < fixmystreet.markers.features.length; i++) {
+ var m = fixmystreet.markers.features[i];
+ var ll = new OpenLayers.LonLat(m.geometry.x, m.geometry.y);
+ heat_layer.addSource(new Heatmap.Source(ll));
+ }
+ heat_layer.redraw();
+ }
+
+ fixmystreet.markers.events.register('loadend', null, create_heat_layer);
+ create_heat_layer();
+ fixmystreet.map.addLayer(heat_layer);
+
+ $('#heatmap_yes').on('click', function() {
+ fixmystreet.markers.setVisibility(false);
+ heat_layer.setVisibility(true);
+ });
+
+ $('#heatmap_no').on('click', function() {
+ heat_layer.setVisibility(false);
+ fixmystreet.markers.setVisibility(true);
+ });
+
+ $('#sort').closest('.report-list-filters').hide();
+
+ $("#wards, #start_date, #end_date").on("change.filters", function() {
+ // If the category or status has changed we need to re-fetch map markers
+ fixmystreet.markers.events.triggerEvent("refresh", {force: true});
+ });
+ $("#filter_categories, #statuses").on("change.filters", function() {
+ if (!fixmystreet.markers.getVisibility()) {
+ // If not visible, still want to trigger change for heatmap
+ fixmystreet.markers.events.triggerEvent("refresh", {force: true});
+ }
+ });
+
+});
diff --git a/web/js/map-OpenLayers.js b/web/js/map-OpenLayers.js
index 984f4b098..ae86269c9 100644
--- a/web/js/map-OpenLayers.js
+++ b/web/js/map-OpenLayers.js
@@ -1055,6 +1055,13 @@ OpenLayers.Strategy.FixMyStreetFixed = OpenLayers.Class(OpenLayers.Strategy.Fixe
// is dragged (modulo a buffer extending outside the viewport).
// This subclass is required so we can pass the 'filter_category' and 'status' query
// params to /around?ajax if the user has filtered the map.
+
+fixmystreet.protocol_params = {
+ filter_category: 'filter_categories',
+ status: 'statuses',
+ sort: 'sort'
+};
+
OpenLayers.Protocol.FixMyStreet = OpenLayers.Class(OpenLayers.Protocol.HTTP, {
initial_page: null,
use_page: false,
@@ -1062,7 +1069,7 @@ OpenLayers.Protocol.FixMyStreet = OpenLayers.Class(OpenLayers.Protocol.HTTP, {
read: function(options) {
// Pass the values of the category, status, and sort fields as query params
options.params = options.params || {};
- $.each({ filter_category: 'filter_categories', status: 'statuses', sort: 'sort' }, function(key, id) {
+ $.each(fixmystreet.protocol_params, function(key, id) {
var val = $('#' + id).val();
if (val && val.length) {
options.params[key] = val.join ? fixmystreet.utils.array_to_csv_line(val) : val;