diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/cobrands/bromley/HeatmapLayer.js | 294 | ||||
-rw-r--r-- | web/cobrands/bromley/_colours.scss | 2 | ||||
-rw-r--r-- | web/cobrands/bromley/base.scss | 4 | ||||
-rw-r--r-- | web/cobrands/bromley/js.js | 75 | ||||
-rw-r--r-- | web/cobrands/fixmystreet-uk-councils/council_validation_rules.js | 2 | ||||
-rw-r--r-- | web/cobrands/fixmystreet-uk-councils/roadworks.js | 1 | ||||
-rw-r--r-- | web/cobrands/sass/_base.scss | 2 | ||||
-rw-r--r-- | web/js/map-OpenLayers.js | 9 |
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; |