From 4c812b6f35eb2d588e9743414eda6eaabcd317f5 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Tue, 19 Jun 2018 23:06:37 -0400 Subject: [PATCH 1/6] Return an abortable request from jsonp_request --- modules/services/streetside.js | 2 +- modules/util/jsonp_request.js | 86 ++++++++++++++++++++-------------- 2 files changed, 53 insertions(+), 35 deletions(-) diff --git a/modules/services/streetside.js b/modules/services/streetside.js index d4dbb2c0d..4b140a57b 100644 --- a/modules/services/streetside.js +++ b/modules/services/streetside.js @@ -247,7 +247,7 @@ function getBubbles(url, tile, callback) { jsCallback: '{callback}' }); - jsonpRequest(urlForRequest, function (data) { + return jsonpRequest(urlForRequest, function (data) { if (!data || data.error) { callback(null); } else { diff --git a/modules/util/jsonp_request.js b/modules/util/jsonp_request.js index b81896a22..ba5d32c27 100644 --- a/modules/util/jsonp_request.js +++ b/modules/util/jsonp_request.js @@ -1,43 +1,61 @@ import { select as d3_select } from 'd3-selection'; - var jsonpCache = {}; window.jsonpCache = jsonpCache; export function jsonpRequest(url, callback) { - - if (window.JSONP_FIX) { - if (window.JSONP_DELAY === 0) { - callback(window.JSONP_FIX); - } else { - setTimeout(function() { - callback(window.JSONP_FIX); - }, window.JSONP_DELAY || 0); - } - return; - } - - function rand() { - var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', - c = '', i = -1; - while (++i < 15) c += chars.charAt(Math.floor(Math.random() * 52)); - return c; - } - - function create(url) { - var e = url.match(/callback=(\w+)/), - c = e ? e[1] : rand(); - jsonpCache[c] = function(data) { - callback(data); - delete jsonpCache[c]; - script.remove(); + var request = { + abort: function() {} }; - return 'jsonpCache.' + c; - } - var cb = create(url), - script = d3_select('head') - .append('script') - .attr('type', 'text/javascript') - .attr('src', url.replace(/(\{|%7B)callback(\}|%7D)/, cb)); + if (window.JSONP_FIX) { + if (window.JSONP_DELAY === 0) { + callback(window.JSONP_FIX); + } else { + var t = window.setTimeout(function() { + callback(window.JSONP_FIX); + }, window.JSONP_DELAY || 0); + + request.abort = function() { window.clearTimeout(t); }; + } + + return request; + } + + function rand() { + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + var c = ''; + var i = -1; + while (++i < 15) c += chars.charAt(Math.floor(Math.random() * 52)); + return c; + } + + function create(url) { + var e = url.match(/callback=(\w+)/); + var c = e ? e[1] : rand(); + + jsonpCache[c] = function(data) { + if (jsonpCache[c]) { + callback(data); + } + finalize(); + }; + + function finalize() { + delete jsonpCache[c]; + script.remove(); + } + + request.abort = finalize; + return 'jsonpCache.' + c; + } + + var cb = create(url); + + var script = d3_select('head') + .append('script') + .attr('type', 'text/javascript') + .attr('src', url.replace(/(\{|%7B)callback(\}|%7D)/, cb)); + + return request; } From 4458b94fc5d0361ae8ecdbbebfc1feaf507d96ce Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Tue, 19 Jun 2018 23:07:59 -0400 Subject: [PATCH 2/6] Add test coverage for streetside service (closes #5081) --- test/index.html | 1 + test/spec/services/streetside.js | 178 +++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 test/spec/services/streetside.js diff --git a/test/index.html b/test/index.html index 186d6ac5f..b2254e67c 100644 --- a/test/index.html +++ b/test/index.html @@ -106,6 +106,7 @@ + diff --git a/test/spec/services/streetside.js b/test/spec/services/streetside.js new file mode 100644 index 000000000..601f62eca --- /dev/null +++ b/test/spec/services/streetside.js @@ -0,0 +1,178 @@ +describe('iD.serviceStreetside', function() { + var dimensions = [64, 64], + context, server, streetside; + + before(function() { + iD.services.streetside = iD.serviceStreetside; + }); + + after(function() { + delete iD.services.streetside; + }); + + beforeEach(function() { + context = iD.Context().assetPath('../dist/'); + context.projection + .scale(667544.214430109) // z14 + .translate([-116508, 0]) // 10,0 + .clipExtent([[0,0], dimensions]); + + server = sinon.fakeServer.create(); + streetside = iD.services.streetside; + streetside.reset(); + }); + + afterEach(function() { + window.JSONP_FIX = undefined; + server.restore(); + }); + + + describe('#init', function() { + it('Initializes cache one time', function() { + var cache = streetside.cache(); + expect(cache).to.have.property('bubbles'); + expect(cache).to.have.property('sequences'); + + streetside.init(); + var cache2 = streetside.cache(); + expect(cache).to.equal(cache2); + }); + }); + + describe('#reset', function() { + it('resets cache', function() { + streetside.cache().foo = 'bar'; + streetside.reset(); + expect(streetside.cache()).to.not.have.property('foo'); + }); + }); + + describe('#loadBubbles', function() { + it('fires loadedBubbles when bubbles are loaded', function() { + // adjust projection so that only one tile is fetched + // (JSONP hack will return the same data for every fetch) + context.projection + .scale(10680707.430881744) // z18 + .translate([-1863988.9381333336, 762.8270222954452]) // 10.002,0.002 + .clipExtent([[0,0], dimensions]); + + var spy = sinon.spy(); + streetside.on('loadedBubbles', spy); + + window.JSONP_DELAY = 0; + window.JSONP_FIX = [{ + elapsed: 0.001 + }, { + id: 1, la: 0, lo: 10.001, al: 0, ro: 0, pi: 0, he: 0, bl: '', + cd: '1/1/2018 12:00:00 PM', ml: 3, nbn: [], pbn: [], rn: [], + pr: undefined, ne: 2 + }, { + id: 2, la: 0, lo: 10.002, al: 0, ro: 0, pi: 0, he: 0, bl: '', + cd: '1/1/2018 12:00:01 PM', ml: 3, nbn: [], pbn: [], rn: [], + pr: 1, ne: 3 + }, { + id: 3, la: 0, lo: 10.003, al: 0, ro: 0, pi: 0, he: 0, bl: '', + cd: '1/1/2018 12:00:02 PM', ml: 3, nbn: [], pbn: [], rn: [], + pr: 2, ne: undefined + } + ]; + + streetside.loadBubbles(context.projection); + expect(spy).to.have.been.calledOnce; + }); + + it('does not load bubbles around null island', function() { + context.projection + .scale(10680707.430881744) // z18 + .translate([0, 0]) + .clipExtent([[0,0], dimensions]); + + var spy = sinon.spy(); + streetside.on('loadedBubbles', spy); + + window.JSONP_DELAY = 0; + window.JSONP_FIX = [{ + elapsed: 0.001 + }, { + id: 1, la: 0, lo: 0, al: 0, ro: 0, pi: 0, he: 0, bl: '', + cd: '1/1/2018 12:00:00 PM', ml: 3, nbn: [], pbn: [], rn: [], + pr: undefined, ne: 2 + }, { + id: 2, la: 0, lo: 0, al: 0, ro: 0, pi: 0, he: 0, bl: '', + cd: '1/1/2018 12:00:01 PM', ml: 3, nbn: [], pbn: [], rn: [], + pr: 1, ne: 3 + }, { + id: 3, la: 0, lo: 0, al: 0, ro: 0, pi: 0, he: 0, bl: '', + cd: '1/1/2018 12:00:02 PM', ml: 3, nbn: [], pbn: [], rn: [], + pr: 2, ne: undefined + } + ]; + + streetside.loadBubbles(context.projection); + expect(spy).to.have.been.not.called; + }); + }); + + + describe('#bubbles', function() { + it('returns bubbles in the visible map area', function() { + var features = [ + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 1, loc: [10, 0], ca: 90, pr: undefined, ne: 2, pano: true, sequenceKey: 1 } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 2, loc: [10, 0], ca: 90, pr: 1, ne: 3, pano: true, sequenceKey: 1 } }, + { minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: 3, loc: [10, 1], ca: 90, pr: 2, ne: undefined, pano: true, sequenceKey: 1 } } + ]; + + streetside.cache().bubbles.rtree.load(features); + var res = streetside.bubbles(context.projection); + + expect(res).to.deep.eql([ + { key: 1, loc: [10, 0], ca: 90, pr: undefined, ne: 2, pano: true, sequenceKey: 1 }, + { key: 2, loc: [10, 0], ca: 90, pr: 1, ne: 3, pano: true, sequenceKey: 1 } + ]); + }); + + it('limits results no more than 3 stacked bubbles in one spot', function() { + var features = [ + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 1, loc: [10, 0], ca: 90, pr: undefined, ne: 2, pano: true, sequence_id: 1 } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 2, loc: [10, 0], ca: 90, pr: 1, ne: 3, pano: true, sequence_id: 1 } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 3, loc: [10, 0], ca: 90, pr: 2, ne: 4, pano: true, sequence_id: 1 } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 4, loc: [10, 0], ca: 90, pr: 3, ne: 5, pano: true, sequence_id: 1 } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 5, loc: [10, 0], ca: 90, pr: 4, ne: undefined, pano: true, sequence_id: 1 } } + ]; + + streetside.cache().bubbles.rtree.load(features); + var res = streetside.bubbles(context.projection); + expect(res).to.have.length.of.at.most(3); + }); + }); + + + describe('#sequences', function() { + it('returns sequence linestrings in the visible map area', function() { + var features = [ + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 1, loc: [10, 0], ca: 90, pr: undefined, ne: 2, pano: true, sequenceKey: 1 } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 2, loc: [10, 0], ca: 90, pr: 1, ne: 3, pano: true, sequenceKey: 1 } }, + { minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: 3, loc: [10, 1], ca: 90, pr: 2, ne: undefined, pano: true, sequenceKey: 1 } } + ]; + + streetside.cache().bubbles.rtree.load(features); + + var seq = { + key: 1, + bubbles: features.map(function(f) { return f.data; }), + geojson: { + type: 'LineString', + properties: { key: 1 }, + coordinates: features.map(function(f) { return f.data.loc; }), + } + }; + + streetside.cache().sequences[1] = seq; + + var res = streetside.sequences(context.projection); + expect(res).to.deep.eql([seq.geojson]); + }); + }); + +}); From 7d678165218a4974e75eb43405a6593f52116cdf Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Wed, 20 Jun 2018 13:40:21 -0400 Subject: [PATCH 3/6] Add `indoor` and `ref` fields to emergency=phone --- data/presets/presets.json | 2 +- data/presets/presets/emergency/phone.json | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/data/presets/presets.json b/data/presets/presets.json index c5aee9d3c..2d0842b22 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -315,7 +315,7 @@ "emergency/fire_extinguisher": {"icon": "fas-fire-extinguisher", "fields": ["indoor", "ref", "operator"], "geometry": ["point", "vertex"], "tags": {"emergency": "fire_extinguisher"}, "name": "Fire Extinguisher"}, "emergency/fire_hydrant": {"icon": "temaki-fire_hydrant", "fields": ["fire_hydrant/type", "fire_hydrant/position", "ref", "operator"], "geometry": ["point", "vertex"], "terms": ["fire plug"], "tags": {"emergency": "fire_hydrant"}, "name": "Fire Hydrant"}, "emergency/life_ring": {"icon": "fas-life-ring", "fields": ["ref", "operator"], "geometry": ["point", "vertex"], "terms": ["life buoy", "kisby ring", "kisbie ring", "perry buoy"], "tags": {"emergency": "life_ring"}, "name": "Life Ring"}, - "emergency/phone": {"icon": "maki-emergency-phone", "fields": ["operator"], "geometry": ["point", "vertex"], "tags": {"emergency": "phone"}, "name": "Emergency Phone"}, + "emergency/phone": {"icon": "maki-emergency-phone", "fields": ["indoor", "ref", "operator"], "geometry": ["point", "vertex"], "tags": {"emergency": "phone"}, "name": "Emergency Phone"}, "emergency/water_tank": {"icon": "maki-water", "fields": ["name", "ref", "operator"], "geometry": ["point", "vertex"], "terms": ["water tank", "cistern", "reservoir"], "tags": {"emergency": "water_tank"}, "name": "Emergency Water Tank"}, "entrance": {"icon": "maki-entrance-alt1", "geometry": ["vertex"], "tags": {"entrance": "*"}, "fields": ["entrance", "access_simple", "address"], "name": "Entrance/Exit"}, "footway/crossing-raised": {"fields": ["crossing", "access", "surface", "kerb", "tactile_paving"], "geometry": ["line"], "tags": {"highway": "footway", "footway": "crossing", "traffic_calming": "table"}, "reference": {"key": "traffic_calming", "value": "table"}, "terms": ["flat top", "hump", "speed", "slow"], "name": "Raised Street Crossing"}, diff --git a/data/presets/presets/emergency/phone.json b/data/presets/presets/emergency/phone.json index 443ce8131..a3046dafe 100644 --- a/data/presets/presets/emergency/phone.json +++ b/data/presets/presets/emergency/phone.json @@ -1,6 +1,8 @@ { "icon": "maki-emergency-phone", "fields": [ + "indoor", + "ref", "operator" ], "geometry": [ From 716a3f7a01b6cf5b0d6e76adda8774caa72a449d Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Wed, 20 Jun 2018 13:56:50 -0400 Subject: [PATCH 4/6] Add a preset for `emergency=first_aid` (closes #5097) --- data/presets.yaml | 5 ++++ data/presets/presets.json | 1 + data/presets/presets/emergency/first_aid.json | 24 +++++++++++++++++++ data/taginfo.json | 7 ++++++ dist/locales/en.json | 4 ++++ svg/fontawesome/fas-medkit.svg | 1 + 6 files changed, 42 insertions(+) create mode 100644 data/presets/presets/emergency/first_aid.json create mode 100644 svg/fontawesome/fas-medkit.svg diff --git a/data/presets.yaml b/data/presets.yaml index 6c6cc4fa7..da1251068 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -3074,6 +3074,11 @@ en: name: Fire Hydrant # 'terms: fire plug' terms: '' + emergency/first_aid: + # emergency=first_aid + name: First Aid Kit + # 'terms: bandage,first aid,med,med kit,medic*,medkit' + terms: '' emergency/life_ring: # emergency=life_ring name: Life Ring diff --git a/data/presets/presets.json b/data/presets/presets.json index 2d0842b22..58baae6df 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -314,6 +314,7 @@ "emergency/defibrillator": {"icon": "maki-defibrillator", "fields": ["indoor", "ref", "operator"], "geometry": ["point", "vertex"], "terms": ["AED"], "tags": {"emergency": "defibrillator"}, "name": "Defibrillator"}, "emergency/fire_extinguisher": {"icon": "fas-fire-extinguisher", "fields": ["indoor", "ref", "operator"], "geometry": ["point", "vertex"], "tags": {"emergency": "fire_extinguisher"}, "name": "Fire Extinguisher"}, "emergency/fire_hydrant": {"icon": "temaki-fire_hydrant", "fields": ["fire_hydrant/type", "fire_hydrant/position", "ref", "operator"], "geometry": ["point", "vertex"], "terms": ["fire plug"], "tags": {"emergency": "fire_hydrant"}, "name": "Fire Hydrant"}, + "emergency/first_aid": {"icon": "fas-medkit", "fields": ["indoor", "ref", "operator"], "geometry": ["point", "vertex"], "terms": ["bandage", "first aid", "med", "med kit", "medic*", "medkit"], "tags": {"emergency": "first_aid"}, "name": "First Aid Kit"}, "emergency/life_ring": {"icon": "fas-life-ring", "fields": ["ref", "operator"], "geometry": ["point", "vertex"], "terms": ["life buoy", "kisby ring", "kisbie ring", "perry buoy"], "tags": {"emergency": "life_ring"}, "name": "Life Ring"}, "emergency/phone": {"icon": "maki-emergency-phone", "fields": ["indoor", "ref", "operator"], "geometry": ["point", "vertex"], "tags": {"emergency": "phone"}, "name": "Emergency Phone"}, "emergency/water_tank": {"icon": "maki-water", "fields": ["name", "ref", "operator"], "geometry": ["point", "vertex"], "terms": ["water tank", "cistern", "reservoir"], "tags": {"emergency": "water_tank"}, "name": "Emergency Water Tank"}, diff --git a/data/presets/presets/emergency/first_aid.json b/data/presets/presets/emergency/first_aid.json new file mode 100644 index 000000000..22fbec12b --- /dev/null +++ b/data/presets/presets/emergency/first_aid.json @@ -0,0 +1,24 @@ +{ + "icon": "fas-medkit", + "fields": [ + "indoor", + "ref", + "operator" + ], + "geometry": [ + "point", + "vertex" + ], + "terms": [ + "bandage", + "first aid", + "med", + "med kit", + "medic*", + "medkit" + ], + "tags": { + "emergency": "first_aid" + }, + "name": "First Aid Kit" +} diff --git a/data/taginfo.json b/data/taginfo.json index 2f6abe472..2a6e8ced1 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -2143,6 +2143,13 @@ "object_types": ["node"], "icon_url": "https://raw.githubusercontent.com/bhousel/temaki/master/icons/fire_hydrant.svg?sanitize=true" }, + { + "key": "emergency", + "value": "first_aid", + "description": "First Aid Kit", + "object_types": ["node"], + "icon_url": "https://raw.githubusercontent.com/openstreetmap/iD/master/svg/fontawesome/fas-medkit.svg?sanitize=true" + }, { "key": "emergency", "value": "life_ring", diff --git a/dist/locales/en.json b/dist/locales/en.json index c0ba6ad9d..53168f20d 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -4072,6 +4072,10 @@ "name": "Fire Hydrant", "terms": "fire plug" }, + "emergency/first_aid": { + "name": "First Aid Kit", + "terms": "bandage,first aid,med,med kit,medic*,medkit" + }, "emergency/life_ring": { "name": "Life Ring", "terms": "life buoy,kisby ring,kisbie ring,perry buoy" diff --git a/svg/fontawesome/fas-medkit.svg b/svg/fontawesome/fas-medkit.svg new file mode 100644 index 000000000..8405214e5 --- /dev/null +++ b/svg/fontawesome/fas-medkit.svg @@ -0,0 +1 @@ + \ No newline at end of file From b238d442a6ca68591f8f2e9ab9c2576a36db52eb Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Thu, 21 Jun 2018 01:33:18 -0400 Subject: [PATCH 5/6] Adjust default zoom for points to z19 (closes #5099) --- modules/renderer/map.js | 2 +- modules/ui/feature_list.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/renderer/map.js b/modules/renderer/map.js index 6b2016c8f..ef3d0c01e 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -718,7 +718,7 @@ export function rendererMap(context) { if (!isFinite(extent.area())) return; var z2 = map.trimmedExtentZoom(extent); - zoomLimits = zoomLimits || [context.minEditableZoom(), 24]; + zoomLimits = zoomLimits || [context.minEditableZoom(), 19]; map.centerZoom(extent.center(), Math.min(Math.max(z2, zoomLimits[0]), zoomLimits[1])); }; diff --git a/modules/ui/feature_list.js b/modules/ui/feature_list.js index c7ba4a233..e3974f926 100644 --- a/modules/ui/feature_list.js +++ b/modules/ui/feature_list.js @@ -322,7 +322,7 @@ export function uiFeatureList(context) { function click(d) { d3_event.preventDefault(); if (d.location) { - context.map().centerZoom([d.location[1], d.location[0]], 20); + context.map().centerZoom([d.location[1], d.location[0]], 19); } else if (d.entity) { if (d.entity.type === 'node') { From 47ef49d99b626ff6678e41700a34c81e6b501593 Mon Sep 17 00:00:00 2001 From: Thomas Hervey Date: Thu, 21 Jun 2018 10:20:09 -0400 Subject: [PATCH 6/6] added: .vscode to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5f7ebb0f3..b69320d41 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ land.html /css/img /test/css /test/img + +\.vscode/