aboutsummaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/cobrands/bromley/HeatmapLayer.js294
-rw-r--r--web/cobrands/bromley/_colours.scss2
-rw-r--r--web/cobrands/bromley/base.scss4
-rw-r--r--web/cobrands/bromley/js.js75
-rw-r--r--web/cobrands/fixmystreet-uk-councils/council_validation_rules.js2
-rw-r--r--web/cobrands/fixmystreet-uk-councils/roadworks.js1
-rw-r--r--web/cobrands/sass/_base.scss2
-rw-r--r--web/js/map-OpenLayers.js9
8 files changed, 385 insertions, 4 deletions
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/_colours.scss b/web/cobrands/bromley/_colours.scss
index 4ea842695..1eb0b8bdb 100644
--- a/web/cobrands/bromley/_colours.scss
+++ b/web/cobrands/bromley/_colours.scss
@@ -8,7 +8,7 @@ $bromley_dark_green: #505050;
$primary: $bromley_blue;
$primary_b: #ffffff;
-$primary_text: #000000;
+$primary_text: #ffffff;
$base_bg: #f4f4f4;
$base_fg: #1a1a1a;
diff --git a/web/cobrands/bromley/base.scss b/web/cobrands/bromley/base.scss
index 720dc1099..16ca15e55 100644
--- a/web/cobrands/bromley/base.scss
+++ b/web/cobrands/bromley/base.scss
@@ -59,6 +59,10 @@ body.frontpage.fullwidthpage {
line-height: normal;
}
+#front-main #postcodeForm .form-hint {
+ color: mix($primary, $primary_text, 30%);
+}
+
.bromley-header {
// These are from Bromley's styles, applied here because it's the closest
// analog to their site header.
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/cobrands/fixmystreet-uk-councils/council_validation_rules.js b/web/cobrands/fixmystreet-uk-councils/council_validation_rules.js
index b13b9be13..9f50f1055 100644
--- a/web/cobrands/fixmystreet-uk-councils/council_validation_rules.js
+++ b/web/cobrands/fixmystreet-uk-councils/council_validation_rules.js
@@ -38,7 +38,7 @@ body_validation_rules = {
maxlength: 50
},
phone: {
- maxlength: 30
+ maxlength: 20
},
email: {
maxlength: 50
diff --git a/web/cobrands/fixmystreet-uk-councils/roadworks.js b/web/cobrands/fixmystreet-uk-councils/roadworks.js
index 538f877c2..354dfaf77 100644
--- a/web/cobrands/fixmystreet-uk-councils/roadworks.js
+++ b/web/cobrands/fixmystreet-uk-councils/roadworks.js
@@ -131,6 +131,7 @@ var roadworks_defaults = {
params.filterstartdate = format_date(date);
date.setMonth(date.getMonth() + 3);
params.filterenddate = format_date(date);
+ params.mapzoom = fixmystreet.map.getZoom() + fixmystreet.zoomOffset;
return params;
}
},
diff --git a/web/cobrands/sass/_base.scss b/web/cobrands/sass/_base.scss
index c3cf3cb1b..d54952028 100644
--- a/web/cobrands/sass/_base.scss
+++ b/web/cobrands/sass/_base.scss
@@ -2485,7 +2485,7 @@ a#geolocate_link.loading, .btn--geolocate.loading {
}
input:checked + label {
- color: $primary_text;
+ color: $primary_text !important;
background: $primary;
border-color: darken($primary, 5%);
background-image: none; // remove gradient
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;