diff options
author | Dave Arter <davea@mysociety.org> | 2018-02-12 09:52:46 +0000 |
---|---|---|
committer | Dave Arter <davea@mysociety.org> | 2018-02-22 11:30:24 +0000 |
commit | 01f7448201d11139ae3a31745b7f26965c7282e7 (patch) | |
tree | c26749c8e1c1f733e4d40d2ddc15d8d70a78a778 | |
parent | 9c8493a3b785d2cda969533c76ac6cca26f3ca00 (diff) |
Populate `usrn` field with USRN of clicked asset, if available
Some cobrands require reports to include the USRN of the clicked
road. This commit allows an asset layer to be added and designated
as a 'USRN provider' by setting its `usrn_field` property when calling
fixmystreet.assets.add.
Initially this feature used OpenLayers' getFeatureFromEvent method,
however that doesn't work if the layer isn't topmost. This is because it
uses the clicked element in the DOM to determine which feature was
clicked. This doesn't work if the layer you're trying to get the feature
from wasn't actually the DOM element that was clicked.
Instead, we add a new method, OpenLayers.Layer.Vector.getFeatureAtPoint
method which takes a Point object and iterates through the features'
geometries to find the matching point.
To make things a little more user-friendly, if an asset isn't clicked
directly we find the closest to the clicked point and use that for the
USRN. To accomplish this, this commit factors out the ‘select nearest
asset’ code into a new method OpenLayers.Layer.Vector.getNearestFeature,
which takes a Point and a distance threshold and finds the nearest
feature.
-rw-r--r-- | web/cobrands/fixmystreet/assets.js | 97 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/fixmystreet.js | 3 | ||||
-rw-r--r-- | web/js/map-OpenLayers.js | 21 |
3 files changed, 105 insertions, 16 deletions
diff --git a/web/cobrands/fixmystreet/assets.js b/web/cobrands/fixmystreet/assets.js index d5dd3bc32..600cd6ab8 100644 --- a/web/cobrands/fixmystreet/assets.js +++ b/web/cobrands/fixmystreet/assets.js @@ -4,6 +4,8 @@ var fixmystreet = fixmystreet || {}; var selected_feature = null; var fault_popup = null; +var selected_usrn = null; +var usrn_field = null; function close_fault_popup() { if (!!fault_popup) { @@ -32,6 +34,11 @@ function asset_selected(e) { return; } + // Pick up the USRN for the location of this asset. NB we do this *before* + // handling the attributes on the selected feature in case the feature has + // its own USRN which should take precedence. + fixmystreet.assets.select_usrn(lonlat); + // Set the extra field to the value of the selected feature $.each(this.fixmystreet.attributes, function (field_name, attribute_name) { var field_value; @@ -164,18 +171,9 @@ function select_nearest_asset() { // No marker to be found so bail out return; } - var closest_feature; - var closest_distance = null; - for (var i = 0; i < this.features.length; i++) { - var candidate = this.features[i]; - var distance = candidate.geometry.distanceTo(marker.geometry); - if (closest_distance === null || distance < closest_distance) { - closest_feature = candidate; - closest_distance = distance; - } - } - if (closest_distance <= threshold && !!closest_feature) { - get_select_control(this).select(closest_feature); + var nearest_feature = this.getNearestFeature(marker.geometry, threshold); + if (nearest_feature) { + get_select_control(this).select(nearest_feature); } } @@ -443,6 +441,35 @@ fixmystreet.assets = { fixmystreet.map.addControl(fixmystreet.assets.controls[i]); fixmystreet.assets.controls[i].activate(); } + }, + + select_usrn: function(lonlat) { + var usrn_providers = fixmystreet.map.getLayersBy('fixmystreet', { + test: function(options) { + return options && options.usrn; + } + }); + if (usrn_providers.length) { + var usrn_layer = usrn_providers[0]; + usrn_field = usrn_layer.fixmystreet.usrn.field; + var point = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat); + var feature = usrn_layer.getFeatureAtPoint(point); + if (feature == null) { + // The click wasn't directly over a road, try and find one + // nearby + feature = usrn_layer.getNearestFeature(point, 10); + } + if (feature !== null) { + selected_usrn = feature.attributes[usrn_layer.fixmystreet.usrn.attribute]; + } else { + selected_usrn = null; + } + fixmystreet.assets.update_usrn_field(); + } + }, + + update_usrn_field: function() { + $("input[name="+usrn_field+"]").val(selected_usrn); } }; @@ -450,4 +477,50 @@ $(function() { fixmystreet.assets.init(); }); +OpenLayers.Layer.Vector.prototype.getFeatureAtPoint = function(point) { + for (var i = 0; i < this.features.length; i++) { + var feature = this.features[i]; + if (!feature.geometry || !feature.geometry.containsPoint) { + continue; + } + if (feature.geometry.containsPoint(point)) { + return feature; + } + } + return null; +}; + + +/* + * Returns this layer's feature that's closest to the given + * OpenLayers.Geometry.Point, as long as it's within <threshold> metres. + * Returns null if no feature meeting these criteria is found. + */ +OpenLayers.Layer.Vector.prototype.getNearestFeature = function(point, threshold) { + var nearest_feature = null; + var nearest_distance = null; + for (var i = 0; i < this.features.length; i++) { + var candidate = this.features[i]; + if (!candidate.geometry || !candidate.geometry.distanceTo) { + continue; + } + var details = candidate.geometry.distanceTo(point, {details: true}); + if (nearest_distance === null || details.distance < nearest_distance) { + nearest_distance = details.distance; + // The units used for details.distance aren't metres, they're + // whatever the map projection uses. Convert to metres in order to + // draw a meaningful comparison to the threshold value. + var p1 = new OpenLayers.Geometry.Point(details.x0, details.y0); + var p2 = new OpenLayers.Geometry.Point(details.x1, details.y1); + var line = new OpenLayers.Geometry.LineString([p1, p2]); + var distance_m = line.getGeodesicLength(this.map.getProjectionObject()); + + if (distance_m <= threshold) { + nearest_feature = candidate; + } + } + } + return nearest_feature; +}; + })(); diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js index 0aa01e483..c7bbc8cad 100644 --- a/web/cobrands/fixmystreet/fixmystreet.js +++ b/web/cobrands/fixmystreet/fixmystreet.js @@ -417,6 +417,9 @@ $.extend(fixmystreet.set_up, { } else { $category_meta.empty(); } + if (fixmystreet.assets) { + fixmystreet.assets.update_usrn_field(); + } }); if (fixmystreet.hooks.update_problem_fields) { diff --git a/web/js/map-OpenLayers.js b/web/js/map-OpenLayers.js index 0f6cca2b5..5ebb9a18e 100644 --- a/web/js/map-OpenLayers.js +++ b/web/js/map-OpenLayers.js @@ -40,19 +40,32 @@ $.extend(fixmystreet.utils, { }; $.extend(fixmystreet.maps, { - // This function might be passed either an OpenLayers.LonLat (so has - // lon and lat), or an OpenLayers.Geometry.Point (so has x and y). update_pin: function(lonlat) { + // This function might be passed either an OpenLayers.LonLat (so has + // lon and lat), or an OpenLayers.Geometry.Point (so has x and y). + if (lonlat.x !== undefined && lonlat.y !== undefined) { + // It's a Point, convert to a LatLon + lonlat = new OpenLayers.LonLat(lonlat.x, lonlat.y); + } + var transformedLonlat = lonlat.clone().transform( fixmystreet.map.getProjectionObject(), new OpenLayers.Projection("EPSG:4326") ); - var lat = transformedLonlat.lat || transformedLonlat.y; - var lon = transformedLonlat.lon || transformedLonlat.x; + var lat = transformedLonlat.lat; + var lon = transformedLonlat.lon; document.getElementById('fixmystreet.latitude').value = lat; document.getElementById('fixmystreet.longitude').value = lon; + + // This tight coupling isn't ideal. A better solution would be for the + // asset code to register an event handler somewhere, but the correct + // place isn't apparent. + if (fixmystreet.assets) { + fixmystreet.assets.select_usrn(lonlat); + } + return { 'url': { 'lon': lon, 'lat': lat }, 'state': { 'lon': lonlat.lon, 'lat': lonlat.lat } |