From 02261c96e0353fff7595dc40fb047ca1477d4566 Mon Sep 17 00:00:00 2001 From: Max Grossman Date: Tue, 18 Dec 2018 12:01:22 -0500 Subject: [PATCH 1/7] adding svg layer... ref #5587 --- modules/svg/geolocate.js | 91 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 modules/svg/geolocate.js diff --git a/modules/svg/geolocate.js b/modules/svg/geolocate.js new file mode 100644 index 0000000000..8a06d769d3 --- /dev/null +++ b/modules/svg/geolocate.js @@ -0,0 +1,91 @@ + +export function svgGeolocate(projection, context, dispatch) { + var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); + var minZoom = 12; + var layer = d3_select(null); + var _mapillary; + + + function init() { + if (svgGeolocate.initialized) return; // run once + svgGeolocate.enabled = false; + svgGeolocate.initialized = true; + } + + function showLayer() { + layer.attr('display', 'block'); + layerOn(); + } + + + function hideLayer() { + throttledRedraw.cancel(); + layer + .transition() + .duration(250) + .style('opacity', 0) + .on('end', function() {}); + } + + function layerOn() { + layer + .style('opacity', 0) + .transition() + .duration(250) + .style('opacity', 1) + .on('end', function () { dispatch.call('change'); }); + + } + + function layerOff() { + // layer.selectAll('.viewfield-group').remove(); + layer.style('display', 'none'); + } + + function update(location) { + + var groups = layer.selectAll('.markers').selectAll('.viewfield-group') + .data([location]); + + } + + function drawLocation(selection) { + var enabled = svgGeolocate.enabled; + + layer = selection.selectAll('.layer-mapillary-signs') + .data([]); + + layer.exit() + .remove(); + + layer = layer.enter() + .append('g') + .attr('class', 'layer-geolocate-point') + .style('display', enabled ? 'block' : 'none') + .merge(layer); + + if (enabled) { + layerOn(); + update(); + } else { + layerOff(); + } + } + + drawLocation.enabled = function(_) { + if (!arguments.length) return svgGeolocate.enabled; + svgGeolocate.enabled = _; + if (svgGeolocate.enabled) { + showLayer(); + } else { + hideLayer(); + } + dispatch.call('change'); + return this; + }; + + + + init(); + return drawLocation; +} From b7f0d86da902342bdb8833eeed9eeca4e408adb1 Mon Sep 17 00:00:00 2001 From: Max Grossman Date: Tue, 18 Dec 2018 14:19:36 -0500 Subject: [PATCH 2/7] working on geolocate layer ref #5587 --- modules/svg/geolocate.js | 40 +++++++++++++++++++++++++++++++--------- modules/svg/layers.js | 4 +++- modules/ui/geolocate.js | 5 +++++ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/modules/svg/geolocate.js b/modules/svg/geolocate.js index 8a06d769d3..228efd7b55 100644 --- a/modules/svg/geolocate.js +++ b/modules/svg/geolocate.js @@ -1,9 +1,10 @@ +import { svgPointTransform } from "./helpers"; export function svgGeolocate(projection, context, dispatch) { var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); var minZoom = 12; var layer = d3_select(null); - var _mapillary; + var _position; function init() { @@ -41,27 +42,46 @@ export function svgGeolocate(projection, context, dispatch) { // layer.selectAll('.viewfield-group').remove(); layer.style('display', 'none'); } + + function transform(d) { + return svgPointTransform(projection)(d); + } + + function update() { + var points = layer.selectAll('.geolocations').selectAll('.geolocation') + .data([_position]); - function update(location) { - var groups = layer.selectAll('.markers').selectAll('.viewfield-group') - .data([location]); + points.enter() + .append('g') + .attr('class', 'point') + .attr('transform', transform); } function drawLocation(selection) { var enabled = svgGeolocate.enabled; - layer = selection.selectAll('.layer-mapillary-signs') + layer = selection.selectAll('.layer-geolocate') .data([]); layer.exit() .remove(); - layer = layer.enter() + var layerEnter = layer.enter() .append('g') - .attr('class', 'layer-geolocate-point') - .style('display', enabled ? 'block' : 'none') + .attr('class', 'layer-geolocation') + .style('display', enabled ? 'block' : 'none'); + + layerEnter + .append('g') + .attr('class', 'geolocations'); + + layerEnter + .append('g') + .attr('class', 'radius'); + + layer = layerEnter .merge(layer); if (enabled) { @@ -74,9 +94,11 @@ export function svgGeolocate(projection, context, dispatch) { drawLocation.enabled = function(_) { if (!arguments.length) return svgGeolocate.enabled; - svgGeolocate.enabled = _; + _position = _; + svgGeolocate.enabled = true; if (svgGeolocate.enabled) { showLayer(); + update(); } else { hideLayer(); } diff --git a/modules/svg/layers.js b/modules/svg/layers.js index 220a53201b..b5663d7788 100644 --- a/modules/svg/layers.js +++ b/modules/svg/layers.js @@ -9,6 +9,7 @@ import { select as d3_select } from 'd3-selection'; import { svgData } from './data'; import { svgDebug } from './debug'; +import { svgGeolocate } from './geolocate'; import { svgStreetside } from './streetside'; import { svgMapillaryImages } from './mapillary_images'; import { svgMapillarySigns } from './mapillary_signs'; @@ -32,7 +33,8 @@ export function svgLayers(projection, context) { { id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) }, { id: 'openstreetcam-images', layer: svgOpenstreetcamImages(projection, context, dispatch) }, { id: 'debug', layer: svgDebug(projection, context, dispatch) }, - { id: 'touch', layer: svgTouch(projection, context, dispatch) } + { id: 'touch', layer: svgTouch(projection, context, dispatch) }, + { id: 'geolocate', layer: svgGeolocate(projection, context, dispatch) } ]; diff --git a/modules/ui/geolocate.js b/modules/ui/geolocate.js index e0efe190de..fe1a0bcd01 100644 --- a/modules/ui/geolocate.js +++ b/modules/ui/geolocate.js @@ -25,6 +25,11 @@ export function uiGeolocate(context) { function success(position) { + var location = { loc: [position.coords.longitude, position.coords.latitude] }, + layer = context.layers().layer('geolocate'); + + layer.enabled(location); + var map = context.map(), extent = geoExtent([position.coords.longitude, position.coords.latitude]) .padByMeters(position.coords.accuracy); From b9ddfca152eda0ea6a77e162b254a21b79de222a Mon Sep 17 00:00:00 2001 From: Max Grossman Date: Wed, 19 Dec 2018 11:06:41 -0500 Subject: [PATCH 3/7] implement point/puck geolocation ref #5587 --- modules/svg/geolocate.js | 88 ++++++++++++++++++++++++++++------------ modules/svg/index.js | 1 + modules/ui/geolocate.js | 35 ++++++++++------ 3 files changed, 85 insertions(+), 39 deletions(-) diff --git a/modules/svg/geolocate.js b/modules/svg/geolocate.js index 228efd7b55..22ad33de86 100644 --- a/modules/svg/geolocate.js +++ b/modules/svg/geolocate.js @@ -1,4 +1,13 @@ -import { svgPointTransform } from "./helpers"; +import { select as d3_select } from 'd3-selection'; + +import { svgPointTransform } from './helpers'; +import { geoMetersToLat } from '../geo'; +import { + geoIdentity as d3_geoIdentity, + geoPath as d3_geoPath, + geoStream as d3_geoStream +} from 'd3-geo'; +import _throttle from 'lodash-es/throttle'; export function svgGeolocate(projection, context, dispatch) { var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); @@ -14,8 +23,7 @@ export function svgGeolocate(projection, context, dispatch) { } function showLayer() { - layer.attr('display', 'block'); - layerOn(); + layer.style('display', 'block'); } @@ -25,7 +33,7 @@ export function svgGeolocate(projection, context, dispatch) { .transition() .duration(250) .style('opacity', 0) - .on('end', function() {}); + .on('end', function () { }); } function layerOn() { @@ -33,81 +41,107 @@ export function svgGeolocate(projection, context, dispatch) { .style('opacity', 0) .transition() .duration(250) - .style('opacity', 1) - .on('end', function () { dispatch.call('change'); }); + .style('opacity', 1); } function layerOff() { - // layer.selectAll('.viewfield-group').remove(); layer.style('display', 'none'); } function transform(d) { return svgPointTransform(projection)(d); } - + + function accuracy(accuracy, loc) { // converts accuracy to pixels... + var degreesRadius = geoMetersToLat(accuracy), + tangentLoc = [loc[0], loc[1] + degreesRadius], + projectedTangent = projection(tangentLoc), + projectedLoc = projection([loc[0], loc[1]]); + + return Math.round(projectedLoc[1] - projectedTangent[1]).toString(); // more south point will have higher pixel value... + } + function update() { - var points = layer.selectAll('.geolocations').selectAll('.geolocation') - .data([_position]); - + var geolocation = { loc: [_position.coords.longitude, _position.coords.latitude] }, + pixelAccuracy = accuracy(_position.coords.accuracy, geolocation.loc); + + var groups = layer.selectAll('.geolocations').selectAll('.geolocation') + .data([geolocation]); + + groups.exit() + .remove(); - points.enter() + var pointsEnter = groups.enter() .append('g') - .attr('class', 'point') + .attr('class', 'geolocation'); + + pointsEnter + .append('circle') + .attr('id', 'geolocate-radius') + .attr('dx', '0') + .attr('dy', '0') + .attr('fill', 'rgb(15,128,225)') + .attr('fill-opacity', '0.3') + .attr('r', '0'); + + pointsEnter + .append('circle') + .attr('dx', '0') + .attr('dy', '0') + .attr('fill', 'rgb(15,128,225)') + .attr('stroke', 'white') + .attr('stroke-width', '1.5') + .attr('r', '6'); + + groups.merge(pointsEnter) .attr('transform', transform); + d3_select('#geolocate-radius').attr('r', pixelAccuracy) } function drawLocation(selection) { var enabled = svgGeolocate.enabled; layer = selection.selectAll('.layer-geolocate') - .data([]); + .data([0]); layer.exit() .remove(); var layerEnter = layer.enter() .append('g') - .attr('class', 'layer-geolocation') + .attr('class', 'layer-geolocate') .style('display', enabled ? 'block' : 'none'); layerEnter .append('g') .attr('class', 'geolocations'); - layerEnter - .append('g') - .attr('class', 'radius'); - layer = layerEnter .merge(layer); if (enabled) { - layerOn(); update(); } else { layerOff(); } } - drawLocation.enabled = function(_) { + drawLocation.enabled = function (position, enabled) { if (!arguments.length) return svgGeolocate.enabled; - _position = _; - svgGeolocate.enabled = true; + _position = position; + svgGeolocate.enabled = enabled; if (svgGeolocate.enabled) { showLayer(); - update(); + layerOn(); } else { hideLayer(); } - dispatch.call('change'); + // dispatch.call('change'); return this; }; - - init(); return drawLocation; } diff --git a/modules/svg/index.js b/modules/svg/index.js index 310acc0587..b444bd7a05 100644 --- a/modules/svg/index.js +++ b/modules/svg/index.js @@ -3,6 +3,7 @@ export { svgData } from './data.js'; export { svgDebug } from './debug.js'; export { svgDefs } from './defs.js'; export { svgIcon } from './icon.js'; +export { svgGeolocate } from './geolocate'; export { svgLabels } from './labels.js'; export { svgLayers } from './layers.js'; export { svgLines } from './lines.js'; diff --git a/modules/ui/geolocate.js b/modules/ui/geolocate.js index fe1a0bcd01..651196d056 100644 --- a/modules/ui/geolocate.js +++ b/modules/ui/geolocate.js @@ -9,32 +9,43 @@ import { uiLoading } from './loading'; export function uiGeolocate(context) { var geoOptions = { enableHighAccuracy: false, timeout: 6000 /* 6sec */ }, locating = uiLoading(context).message(t('geolocate.locating')).blocking(true), + layer = context.layers().layer('geolocate'), + position, + extent, timeoutId; function click() { if (context.inIntro()) return; context.enter(modeBrowse(context)); - context.container().call(locating); - navigator.geolocation.getCurrentPosition(success, error, geoOptions); - + if (!layer.enabled()) { + if (!position) { + context.container().call(locating); + navigator.geolocation.getCurrentPosition(success, error, geoOptions); + } else { + zoomTo(); + } + } else { + layer.enabled(null, false); + } // This timeout ensures that we still call finish() even if // the user declines to share their location in Firefox timeoutId = setTimeout(finish, 10000 /* 10sec */ ); } + function zoomTo() { + var map = context.map(); + layer.enabled(position, true); + map.centerZoom(extent.center(), Math.min(20, map.extentZoom(extent))); + } - function success(position) { - var location = { loc: [position.coords.longitude, position.coords.latitude] }, - layer = context.layers().layer('geolocate'); - - layer.enabled(location); - var map = context.map(), - extent = geoExtent([position.coords.longitude, position.coords.latitude]) - .padByMeters(position.coords.accuracy); + function success(geolocation) { + position = geolocation; + extent = geoExtent([position.coords.longitude, position.coords.latitude]) + .padByMeters(position.coords.accuracy); - map.centerZoom(extent.center(), Math.min(20, map.extentZoom(extent))); + zoomTo(); finish(); } From b010f421258ac4ac9a51a7888c8661d65220135a Mon Sep 17 00:00:00 2001 From: Max Grossman Date: Wed, 19 Dec 2018 11:14:03 -0500 Subject: [PATCH 4/7] update layers test for new geolocate svg layer ref #5587 --- test/spec/svg/layers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/spec/svg/layers.js b/test/spec/svg/layers.js index 2a574c1c1c..46c2bfdbbc 100644 --- a/test/spec/svg/layers.js +++ b/test/spec/svg/layers.js @@ -26,7 +26,7 @@ describe('iD.svgLayers', function () { it('creates default data layers', function () { container.call(iD.svgLayers(projection, context)); var nodes = container.selectAll('svg .data-layer').nodes(); - expect(nodes.length).to.eql(9); + expect(nodes.length).to.eql(10); expect(d3.select(nodes[0]).classed('osm')).to.be.true; expect(d3.select(nodes[1]).classed('notes')).to.be.true; expect(d3.select(nodes[2]).classed('data')).to.be.true; @@ -36,6 +36,7 @@ describe('iD.svgLayers', function () { expect(d3.select(nodes[6]).classed('openstreetcam-images')).to.be.true; expect(d3.select(nodes[7]).classed('debug')).to.be.true; expect(d3.select(nodes[8]).classed('touch')).to.be.true; + expect(d3.select(nodes[9]).classed('geolocate')).to.be.true; }); }); From 83c0843e574cd1a74b3ddf6026d97c704644b2e0 Mon Sep 17 00:00:00 2001 From: Max Grossman Date: Wed, 19 Dec 2018 11:23:19 -0500 Subject: [PATCH 5/7] just call accuracy function when setting r attr ref #5587 --- modules/svg/geolocate.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/svg/geolocate.js b/modules/svg/geolocate.js index 22ad33de86..b4d9647024 100644 --- a/modules/svg/geolocate.js +++ b/modules/svg/geolocate.js @@ -63,9 +63,8 @@ export function svgGeolocate(projection, context, dispatch) { } function update() { - var geolocation = { loc: [_position.coords.longitude, _position.coords.latitude] }, - pixelAccuracy = accuracy(_position.coords.accuracy, geolocation.loc); - + var geolocation = { loc: [_position.coords.longitude, _position.coords.latitude] }; + var groups = layer.selectAll('.geolocations').selectAll('.geolocation') .data([geolocation]); @@ -97,7 +96,7 @@ export function svgGeolocate(projection, context, dispatch) { groups.merge(pointsEnter) .attr('transform', transform); - d3_select('#geolocate-radius').attr('r', pixelAccuracy) + d3_select('#geolocate-radius').attr('r', accuracy(_position.coords.accuracy, geolocation.loc)); } function drawLocation(selection) { From 3755951009544863e4bed44626b34d352170c6b4 Mon Sep 17 00:00:00 2001 From: Max Grossman Date: Wed, 19 Dec 2018 11:24:26 -0500 Subject: [PATCH 6/7] remove unused vars ref #5587 --- modules/svg/geolocate.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/modules/svg/geolocate.js b/modules/svg/geolocate.js index b4d9647024..22ec4092fe 100644 --- a/modules/svg/geolocate.js +++ b/modules/svg/geolocate.js @@ -2,16 +2,10 @@ import { select as d3_select } from 'd3-selection'; import { svgPointTransform } from './helpers'; import { geoMetersToLat } from '../geo'; -import { - geoIdentity as d3_geoIdentity, - geoPath as d3_geoPath, - geoStream as d3_geoStream -} from 'd3-geo'; import _throttle from 'lodash-es/throttle'; export function svgGeolocate(projection, context, dispatch) { var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); - var minZoom = 12; var layer = d3_select(null); var _position; @@ -64,7 +58,7 @@ export function svgGeolocate(projection, context, dispatch) { function update() { var geolocation = { loc: [_position.coords.longitude, _position.coords.latitude] }; - + var groups = layer.selectAll('.geolocations').selectAll('.geolocation') .data([geolocation]); From 399e7781c1d409ab467d273523dc2f3dd566df59 Mon Sep 17 00:00:00 2001 From: Max Grossman Date: Wed, 19 Dec 2018 11:54:39 -0500 Subject: [PATCH 7/7] remove dispatched changes and re-order layers ref #5587 --- modules/svg/geolocate.js | 9 +++------ modules/svg/layers.js | 4 ++-- test/spec/svg/layers.js | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/modules/svg/geolocate.js b/modules/svg/geolocate.js index 22ec4092fe..f5ca10f97c 100644 --- a/modules/svg/geolocate.js +++ b/modules/svg/geolocate.js @@ -5,7 +5,6 @@ import { geoMetersToLat } from '../geo'; import _throttle from 'lodash-es/throttle'; export function svgGeolocate(projection, context, dispatch) { - var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); var layer = d3_select(null); var _position; @@ -22,12 +21,10 @@ export function svgGeolocate(projection, context, dispatch) { function hideLayer() { - throttledRedraw.cancel(); layer .transition() .duration(250) - .style('opacity', 0) - .on('end', function () { }); + .style('opacity', 0); } function layerOn() { @@ -53,7 +50,8 @@ export function svgGeolocate(projection, context, dispatch) { projectedTangent = projection(tangentLoc), projectedLoc = projection([loc[0], loc[1]]); - return Math.round(projectedLoc[1] - projectedTangent[1]).toString(); // more south point will have higher pixel value... + // southern most point will have higher pixel value... + return Math.round(projectedLoc[1] - projectedTangent[1]).toString(); } function update() { @@ -131,7 +129,6 @@ export function svgGeolocate(projection, context, dispatch) { } else { hideLayer(); } - // dispatch.call('change'); return this; }; diff --git a/modules/svg/layers.js b/modules/svg/layers.js index b5663d7788..4d0b9f7d22 100644 --- a/modules/svg/layers.js +++ b/modules/svg/layers.js @@ -33,8 +33,8 @@ export function svgLayers(projection, context) { { id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) }, { id: 'openstreetcam-images', layer: svgOpenstreetcamImages(projection, context, dispatch) }, { id: 'debug', layer: svgDebug(projection, context, dispatch) }, - { id: 'touch', layer: svgTouch(projection, context, dispatch) }, - { id: 'geolocate', layer: svgGeolocate(projection, context, dispatch) } + { id: 'geolocate', layer: svgGeolocate(projection, context, dispatch) }, + { id: 'touch', layer: svgTouch(projection, context, dispatch) } ]; diff --git a/test/spec/svg/layers.js b/test/spec/svg/layers.js index 46c2bfdbbc..f2277229cd 100644 --- a/test/spec/svg/layers.js +++ b/test/spec/svg/layers.js @@ -35,8 +35,8 @@ describe('iD.svgLayers', function () { expect(d3.select(nodes[5]).classed('mapillary-signs')).to.be.true; expect(d3.select(nodes[6]).classed('openstreetcam-images')).to.be.true; expect(d3.select(nodes[7]).classed('debug')).to.be.true; - expect(d3.select(nodes[8]).classed('touch')).to.be.true; - expect(d3.select(nodes[9]).classed('geolocate')).to.be.true; + expect(d3.select(nodes[8]).classed('geolocate')).to.be.true; + expect(d3.select(nodes[9]).classed('touch')).to.be.true; }); });