diff --git a/modules/services/mapillary.js b/modules/services/mapillary.js index 1988a4321..125143e89 100644 --- a/modules/services/mapillary.js +++ b/modules/services/mapillary.js @@ -2,192 +2,97 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; import { select as d3_select } from 'd3-selection'; +import Protobuf from 'pbf'; import RBush from 'rbush'; +import vt from '@mapbox/vector-tile'; import { geoExtent, geoScaleToZoom } from '../geo'; -import { utilArrayUnion, utilQsString, utilRebind, utilTiler, utilStringQs } from '../util'; +import { utilQsString, utilRebind, utilTiler, utilStringQs } from '../util'; +const imageDetectionUrl = 'https://a.mapillary.com/v3/image_detections'; +const tileUrl = 'https://tiles3.mapillary.com/v0.1/{z}/{x}/{y}.mvt'; +const mapFeatureTileUrl = 'https://a.mapillary.com/v3/tiles/map_features/{z}/{x}/{y}.mvt'; +const clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi'; +const mapFeatureValues = [ + 'construction--flat--crosswalk-plain', + 'marking--discrete--crosswalk-zebra', + 'object--banner', + 'object--bench', + 'object--bike-rack', + 'object--billboard', + 'object--catch-basin', + 'object--cctv-camera', + 'object--fire-hydrant', + 'object--mailbox', + 'object--manhole', + 'object--phone-booth', + 'object--sign--advertisement', + 'object--sign--information', + 'object--sign--store', + 'object--street-light', + 'object--support--utility-pole', + 'object--traffic-light--*', + 'object--traffic-light--pedestrians', + 'object--trash-can' +].join(','); -var apibase = 'https://a.mapillary.com/v3/'; -var viewercss = 'mapillary-js/mapillary.min.css'; -var viewerjs = 'mapillary-js/mapillary.min.js'; -var clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi'; -var mapFeatureConfig = { - values: [ - 'construction--flat--crosswalk-plain', - 'marking--discrete--crosswalk-zebra', - 'object--banner', - 'object--bench', - 'object--bike-rack', - 'object--billboard', - 'object--catch-basin', - 'object--cctv-camera', - 'object--fire-hydrant', - 'object--mailbox', - 'object--manhole', - 'object--phone-booth', - 'object--sign--advertisement', - 'object--sign--information', - 'object--sign--store', - 'object--street-light', - 'object--support--utility-pole', - 'object--traffic-light--*', - 'object--traffic-light--pedestrians', - 'object--trash-can' - ].join(',') -}; -var maxResults = 1000; -var tileZoom = 14; -var tiler = utilTiler().zoomExtent([tileZoom, tileZoom]).skipNullIsland(true); -var dispatch = d3_dispatch('change', 'loadedImages', 'loadedSigns', 'loadedMapFeatures', 'bearingChanged', 'nodeChanged'); -var _mlyFallback = false; -var _mlyCache; -var _mlyClicks; -var _mlyActiveImage; -var _mlySelectedImageKey; -var _mlyViewer; -var _mlyViewerFilter = ['all']; -var _loadViewerPromise; -var _mlyHighlightedDetection; -var _mlyShowFeatureDetections = false; -var _mlyShowSignDetections = false; +const viewercss = 'mapillary-js/mapillary.min.css'; +const viewerjs = 'mapillary-js/mapillary.min.js'; +const minZoom = 14; +const dispatch = d3_dispatch('change', 'loadedImages', 'loadedSigns', 'loadedMapFeatures', 'bearingChanged', 'nodeChanged'); + +let _loadViewerPromise; +let _mlyActiveImage; +let _mlyCache; +let _mlyClicks; +let _mlyFallback = false; +let _mlyHighlightedDetection; +let _mlyShowFeatureDetections = false; +let _mlyShowSignDetections = false; +let _mlyViewer; +let _mlyViewerFilter = ['all']; function abortRequest(controller) { controller.abort(); } -function loadTiles(which, url, projection) { - var currZoom = Math.floor(geoScaleToZoom(projection.scale())); - var tiles = tiler.getTiles(projection); - // abort inflight requests that are no longer needed - var cache = _mlyCache[which]; - Object.keys(cache.inflight).forEach(function(k) { - var wanted = tiles.find(function(tile) { return k.indexOf(tile.id + ',') === 0; }); - if (!wanted) { - abortRequest(cache.inflight[k]); - delete cache.inflight[k]; - } - }); +function loadTiles(which, url, maxZoom, projection) { + const tiler = utilTiler().zoomExtent([minZoom, maxZoom]).skipNullIsland(true); + const tiles = tiler.getTiles(projection); tiles.forEach(function(tile) { - loadNextTilePage(which, currZoom, url, tile); + loadTile(which, url, tile); }); } -function loadNextTilePage(which, currZoom, url, tile) { - var cache = _mlyCache[which]; - var rect = tile.extent.rectangle(); - var maxPages = maxPageAtZoom(currZoom); - var nextPage = cache.nextPage[tile.id] || 0; - var nextURL = cache.nextURL[tile.id] || url + - utilQsString({ - per_page: maxResults, - page: nextPage, - client_id: clientId, - bbox: [rect[0], rect[1], rect[2], rect[3]].join(','), - }); +function loadTile(which, url, tile) { + const cache = _mlyCache.requests; + const tileId = `${tile.id}-${which}`; + if (cache.loaded[tileId] || cache.inflight[tileId]) return; + const controller = new AbortController(); + cache.inflight[tileId] = controller; + const requestUrl = url.replace('{x}', tile.xyz[0]) + .replace('{y}', tile.xyz[1]) + .replace('{z}', tile.xyz[2]); - if (nextPage > maxPages) return; - - var id = tile.id + ',' + String(nextPage); - if (cache.loaded[id] || cache.inflight[id]) return; - - var controller = new AbortController(); - cache.inflight[id] = controller; - - var options = { - method: 'GET', - signal: controller.signal, - headers: { 'Content-Type': 'application/json' } - }; - - fetch(nextURL, options) + fetch(requestUrl, { signal: controller.signal }) .then(function(response) { if (!response.ok) { throw new Error(response.status + ' ' + response.statusText); } - var linkHeader = response.headers.get('Link'); - if (linkHeader) { - var pagination = parsePagination(linkHeader); - if (pagination.next) { - cache.nextURL[tile.id] = pagination.next; - } - } - return response.json(); + cache.loaded[tileId] = true; + delete cache.inflight[tileId]; + return response.arrayBuffer(); }) .then(function(data) { - cache.loaded[id] = true; - delete cache.inflight[id]; - if (!data || !data.features || !data.features.length) { + if (!data) { throw new Error('No Data'); } + loadTileDataToCache(data, tile, which); - var features = data.features.map(function(feature) { - var loc = feature.geometry.coordinates; - var d; - - // An image (shown as a green dot on the map) is a single street photo with extra - // information such as location, camera angle (CA), camera model, and so on. - // Each image feature is a GeoJSON Point - if (which === 'images') { - d = { - loc: loc, - key: feature.properties.key, - ca: feature.properties.ca, - captured_at: feature.properties.captured_at, - captured_by: feature.properties.username, - pano: feature.properties.pano - }; - - cache.forImageKey[d.key] = d; // cache imageKey -> image - - // Mapillary organizes images as sequences. A sequence of images are continuously captured - // by a user at a give time. Sequences are shown on the map as green lines. - // Each sequence feature is a GeoJSON LineString - } else if (which === 'sequences') { - var sequenceKey = feature.properties.key; - cache.lineString[sequenceKey] = feature; // cache sequenceKey -> lineString - feature.properties.coordinateProperties.image_keys.forEach(function(imageKey) { - cache.forImageKey[imageKey] = sequenceKey; // cache imageKey -> sequenceKey - }); - return false; // because no `d` data worth loading into an rbush - - // A map feature is a real world object that can be shown on a map. It could be any object - // recognized from images, manually added in images, or added on the map. - // Each map feature is a GeoJSON Point (located where the feature is) - } else if (which === 'map_features' || which === 'points') { - d = { - loc: loc, - key: feature.properties.key, - value: feature.properties.value, - detections: feature.properties.detections, - direction: feature.properties.direction, - accuracy: feature.properties.accuracy, - first_seen_at: feature.properties.first_seen_at, - last_seen_at: feature.properties.last_seen_at - }; - } - - return { - minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d - }; - - }).filter(Boolean); - - if (cache.rtree && features) { - cache.rtree.load(features); - } - - if (data.features.length === maxResults) { // more pages to load - cache.nextPage[tile.id] = nextPage + 1; - loadNextTilePage(which, currZoom, url, tile); - } else { - cache.nextPage[tile.id] = Infinity; // no more pages to load - } - - if (which === 'images' || which === 'sequences') { + if (which === 'images') { dispatch.call('loadedImages'); } else if (which === 'map_features') { dispatch.call('loadedSigns'); @@ -196,20 +101,99 @@ function loadNextTilePage(which, currZoom, url, tile) { } }) .catch(function() { - cache.loaded[id] = true; - delete cache.inflight[id]; + cache.loaded[tileId] = true; + delete cache.inflight[tileId]; }); } +function loadTileDataToCache(data, tile, which) { + const vectorTile = new vt.VectorTile(new Protobuf(data)); + let features, + cache, + layer, + i, + feature, + loc, + d; + + if (vectorTile.layers.hasOwnProperty('mapillary-images')) { + features = []; + cache = _mlyCache.images; + layer = vectorTile.layers['mapillary-images']; + + for (i = 0; i < layer.length; i++) { + feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]); + loc = feature.geometry.coordinates; + d = { + loc: loc, + key: feature.properties.key, + ca: feature.properties.ca, + captured_at: feature.properties.captured_at, + captured_by: feature.properties.userkey, + pano: feature.properties.pano, + skey: feature.properties.skey, + }; + cache.forImageKey[d.key] = d; + features.push({ + minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d + }); + } + if (cache.rtree) { + cache.rtree.load(features); + } + } + + if (vectorTile.layers.hasOwnProperty('mapillary-sequences')) { + features = []; + cache = _mlyCache.sequences; + layer = vectorTile.layers['mapillary-sequences']; + + for (i = 0; i < layer.length; i++) { + feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]); + if (cache.lineString[feature.properties.key]) { + cache.lineString[feature.properties.key].push(feature); + } else { + cache.lineString[feature.properties.key] = [feature]; + } + } + } + + if (vectorTile.layers.hasOwnProperty('mapillary-map-features')) { + features = []; + cache = _mlyCache[which]; + layer = vectorTile.layers['mapillary-map-features']; + + for (i = 0; i < layer.length; i++) { + feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]); + loc = feature.geometry.coordinates; + d = { + loc: loc, + key: feature.properties.key, + value: feature.properties.value, + detections: JSON.parse(feature.properties.detections), + first_seen_at: feature.properties.first_seen_at, + last_seen_at: feature.properties.last_seen_at + }; + features.push({ + minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d + }); + } + if (cache.rtree) { + cache.rtree.load(features); + } + } +} + + function loadData(which, url) { - var cache = _mlyCache[which]; - var options = { + const cache = _mlyCache[which]; + const options = { method: 'GET', headers: { 'Content-Type': 'application/json' } }; - var nextUrl = url + '&client_id=' + clientId; - return fetch(nextUrl, options) + + return fetch(url, options) .then(function(response) { if (!response.ok) { throw new Error(response.status + ' ' + response.statusText); @@ -222,59 +206,28 @@ function loadData(which, url) { } data.features.forEach(function(feature) { - var d; - if (which === 'image_detections') { - d = { + const imageKey = feature.properties.image_key; + if (!cache.forImageKey[imageKey]) { + cache.forImageKey[imageKey] = []; + } + cache.forImageKey[imageKey].push({ key: feature.properties.key, image_key: feature.properties.image_key, value: feature.properties.value, shape: feature.properties.shape - }; - - if (!cache.forImageKey[d.image_key]) { - cache.forImageKey[d.image_key] = []; - } - cache.forImageKey[d.image_key].push(d); + }); } }); }); } -function maxPageAtZoom(z) { - if (z < 15) return 2; - if (z === 15) return 5; - if (z === 16) return 10; - if (z === 17) return 20; - if (z === 18) return 40; - if (z > 18) return 80; -} - - -// extract links to pages of API results -function parsePagination(links) { - return links.split(',').map(function(rel) { - var elements = rel.split(';'); - if (elements.length === 2) { - return [ - /<(.+)>/.exec(elements[0])[1], - /rel="(.+)"/.exec(elements[1])[1] - ]; - } else { - return ['','']; - } - }).reduce(function(pagination, val) { - pagination[val[1]] = val[0]; - return pagination; - }, {}); -} - // partition viewport into higher zoom tiles function partitionViewport(projection) { - var z = geoScaleToZoom(projection.scale()); - var z2 = (Math.ceil(z * 2) / 2) + 2.5; // round to next 0.5 and add 2.5 - var tiler = utilTiler().zoomExtent([z2, z2]); + const z = geoScaleToZoom(projection.scale()); + const z2 = (Math.ceil(z * 2) / 2) + 2.5; // round to next 0.5 and add 2.5 + const tiler = utilTiler().zoomExtent([z2, z2]); return tiler.getTiles(projection) .map(function(tile) { return tile.extent; }); @@ -287,7 +240,7 @@ function searchLimited(limit, projection, rtree) { return partitionViewport(projection) .reduce(function(result, extent) { - var found = rtree.search(extent.bbox()) + const found = rtree.search(extent.bbox()) .slice(0, limit) .map(function(d) { return d.data; }); @@ -297,7 +250,7 @@ function searchLimited(limit, projection, rtree) { export default { - + // initialize Mapillary init: function() { if (!_mlyCache) { this.reset(); @@ -306,93 +259,86 @@ export default { this.event = utilRebind(this, dispatch, 'on'); }, + // reset cache and state reset: function() { if (_mlyCache) { - Object.values(_mlyCache.images.inflight).forEach(abortRequest); - Object.values(_mlyCache.image_detections.inflight).forEach(abortRequest); - Object.values(_mlyCache.map_features.inflight).forEach(abortRequest); - Object.values(_mlyCache.points.inflight).forEach(abortRequest); - Object.values(_mlyCache.sequences.inflight).forEach(abortRequest); + Object.values(_mlyCache.requests.inflight).forEach(abortRequest); } _mlyCache = { - images: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: new RBush(), forImageKey: {} }, - image_detections: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, forImageKey: {} }, - map_features: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: new RBush() }, - points: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: new RBush() }, - sequences: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: new RBush(), forImageKey: {}, lineString: {} } + images: { rtree: new RBush(), forImageKey: {} }, + image_detections: { forImageKey: {} }, + map_features: { rtree: new RBush() }, + points: { rtree: new RBush() }, + sequences: { rtree: new RBush(), lineString: {} }, + requests: { loaded: {}, inflight: {} } }; - _mlySelectedImageKey = null; _mlyActiveImage = null; _mlyClicks = []; }, - + // get visible images images: function(projection) { - var limit = 5; + const limit = 5; return searchLimited(limit, projection, _mlyCache.images.rtree); }, - + /** + * get visible traffic signs + */ signs: function(projection) { - var limit = 5; + const limit = 5; return searchLimited(limit, projection, _mlyCache.map_features.rtree); }, - + // get visible map (point) features mapFeatures: function(projection) { - var limit = 5; + const limit = 5; return searchLimited(limit, projection, _mlyCache.points.rtree); }, - + // get cached image by key cachedImage: function(imageKey) { return _mlyCache.images.forImageKey[imageKey]; }, - + // get visible sequences sequences: function(projection) { - var viewport = projection.clipExtent(); - var min = [viewport[0][0], viewport[1][1]]; - var max = [viewport[1][0], viewport[0][1]]; - var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox(); - var sequenceKeys = {}; - - // all sequences for images in viewport + const viewport = projection.clipExtent(); + const min = [viewport[0][0], viewport[1][1]]; + const max = [viewport[1][0], viewport[0][1]]; + const bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox(); + const sequenceKeys = {}; + let lineStrings = []; + // find sequences for images in viewport _mlyCache.images.rtree.search(bbox) .forEach(function(d) { - var sequenceKey = _mlyCache.sequences.forImageKey[d.data.key]; - if (sequenceKey) { - sequenceKeys[sequenceKey] = true; + if (d.data.skey) { + sequenceKeys[d.data.skey] = true; } }); - // Return lineStrings for the sequences - return Object.keys(sequenceKeys).map(function(sequenceKey) { - return _mlyCache.sequences.lineString[sequenceKey]; + Object.keys(sequenceKeys).forEach(function(sequenceKey) { + lineStrings = lineStrings.concat(_mlyCache.sequences.lineString[sequenceKey]); }); - }, - - signsSupported: function() { - return true; + return lineStrings; }, loadImages: function(projection) { - loadTiles('images', apibase + 'images?sort_by=key&', projection); - loadTiles('sequences', apibase + 'sequences?sort_by=key&', projection); + loadTiles('images', tileUrl, 14, projection); }, loadSigns: function(projection) { - loadTiles('map_features', apibase + 'map_features?layers=trafficsigns&min_nbr_image_detections=2&sort_by=key&', projection); + loadTiles('map_features', `${mapFeatureTileUrl}?layers=trafficsigns&per_page=1000&client_id=${clientId}`, 18, projection); }, loadMapFeatures: function(projection) { - loadTiles('points', apibase + 'map_features?layers=points&min_nbr_image_detections=2&sort_by=key&values=' + mapFeatureConfig.values + '&', projection); + loadTiles('points', `${mapFeatureTileUrl}?layers=points&per_page=1000&client_id=${clientId}`, 18, projection); }, @@ -400,7 +346,7 @@ export default { if (_loadViewerPromise) return _loadViewerPromise; // add mly-wrapper - var wrap = context.container().select('.photoviewer') + const wrap = context.container().select('.photoviewer') .selectAll('.mly-wrapper') .data([0]); @@ -410,18 +356,17 @@ export default { .attr('class', 'photo-wrapper mly-wrapper') .classed('hide', true); - var that = this; + const that = this; _loadViewerPromise = new Promise((resolve, reject) => { - - var loadedCount = 0; + let loadedCount = 0; function loaded() { loadedCount += 1; // wait until both files are loaded if (loadedCount === 2) resolve(); } - var head = d3_select('head'); + const head = d3_select('head'); // load mapillary-viewercss head.selectAll('#ideditor-mapillary-viewercss') @@ -460,24 +405,28 @@ export default { return _loadViewerPromise; }, + loadSignResources: function(context) { context.ui().svgDefs.addSprites(['mapillary-sprite'], false /* don't override colors */ ); return this; }, + loadObjectResources: function(context) { context.ui().svgDefs.addSprites(['mapillary-object-sprite'], false /* don't override colors */ ); return this; }, + // remove previous detections in viewer resetTags: function() { if (_mlyViewer && !_mlyFallback) { - _mlyViewer.getComponent('tag').removeAll(); // remove previous detections + _mlyViewer.getComponent('tag').removeAll(); } }, + // show map feature detections in viewer showFeatureDetections: function(value) { _mlyShowFeatureDetections = value; if (!_mlyShowFeatureDetections && !_mlyShowSignDetections) { @@ -486,6 +435,7 @@ export default { }, + // show traffic sign detections in viewer showSignDetections: function(value) { _mlyShowSignDetections = value; if (!_mlyShowFeatureDetections && !_mlyShowSignDetections) { @@ -493,24 +443,22 @@ export default { } }, + + // apply filter to viewer filterViewer: function(context) { - var showsPano = context.photos().showsPanoramic(); - var showsFlat = context.photos().showsFlat(); - var fromDate = context.photos().fromDate(); - var toDate = context.photos().toDate(); - var usernames = context.photos().usernames(); - var filter = ['all']; + const showsPano = context.photos().showsPanoramic(); + const showsFlat = context.photos().showsFlat(); + const fromDate = context.photos().fromDate(); + const toDate = context.photos().toDate(); + const filter = ['all']; if (!showsPano) filter.push(['==', 'pano', false]); if (!showsFlat && showsPano) filter.push(['==', 'pano', true]); - if (usernames && usernames.length) filter.push(['==', 'username', usernames[0]]); if (fromDate) { - var fromTimestamp = new Date(fromDate).getTime(); - filter.push(['>=', 'capturedAt', fromTimestamp]); + filter.push(['>=', 'capturedAt', new Date(fromDate).getTime()]); } if (toDate) { - var toTimestamp = new Date(toDate).getTime(); - filter.push(['>=', 'capturedAt', toTimestamp]); + filter.push(['>=', 'capturedAt', new Date(toDate).getTime()]); } if (_mlyViewer) { @@ -523,10 +471,10 @@ export default { showViewer: function(context) { - var wrap = context.container().select('.photoviewer') + const wrap = context.container().select('.photoviewer') .classed('hide', false); - var isHidden = wrap.selectAll('.photo-wrapper.mly-wrapper.hide').size(); + const isHidden = wrap.selectAll('.photo-wrapper.mly-wrapper.hide').size(); if (isHidden && _mlyViewer) { wrap @@ -546,13 +494,12 @@ export default { hideViewer: function(context) { _mlyActiveImage = null; - _mlySelectedImageKey = null; if (!_mlyFallback && _mlyViewer) { _mlyViewer.getComponent('sequence').stop(); } - var viewer = context.container().select('.photoviewer'); + const viewer = context.container().select('.photoviewer'); if (!viewer.empty()) viewer.datum(null); viewer @@ -563,17 +510,17 @@ export default { this.updateUrlImage(null); dispatch.call('nodeChanged'); + dispatch.call('loadedMapFeatures'); + dispatch.call('loadedSigns'); return this.setStyles(context, null, true); }, - parsePagination: parsePagination, - - + // update the URL with current image key updateUrlImage: function(imageKey) { if (!window.mocha) { - var hash = utilStringQs(window.location.hash); + const hash = utilStringQs(window.location.hash); if (imageKey) { hash.photo = 'mapillary/' + imageKey; } else { @@ -584,6 +531,7 @@ export default { }, + // highlight the detection in the viewer that is related to the clicked map feature highlightDetection: function(detection) { if (detection) { _mlyHighlightedDetection = detection.detection_key; @@ -594,18 +542,18 @@ export default { initViewer: function(context) { - var that = this; + const that = this; if (!window.Mapillary) return; - var opts = { + const opts = { apiClient: clientId, - container: 'ideditor-mly', baseImageSize: 320, component: { cover: false, keyboard: false, tag: true - } + }, + container: 'ideditor-mly', }; // Disable components requiring WebGL support @@ -646,20 +594,15 @@ export default { // function nodeChanged(node) { that.resetTags(); - var clicks = _mlyClicks; - var index = clicks.indexOf(node.key); - var selectedKey = _mlySelectedImageKey; + const clicks = _mlyClicks; + const index = clicks.indexOf(node.key); that.setActiveImage(node); + that.setStyles(context, null, true); if (index > -1) { // `nodechanged` initiated from clicking on a marker.. clicks.splice(index, 1); // remove the click - // If `node.key` matches the current _mlySelectedImageKey, call `selectImage()` - // one more time to update the detections and attribution.. - if (node.key === selectedKey) { - that.selectImage(context, _mlySelectedImageKey, true); - } } else { // `nodechanged` initiated from the Mapillary viewer controls.. - var loc = node.computedLatLon ? [node.computedLatLon.lon, node.computedLatLon.lat] : [node.latLon.lon, node.latLon.lat]; + const loc = node.computedLatLon ? [node.computedLatLon.lon, node.computedLatLon.lat] : [node.latLon.lon, node.latLon.lat]; context.map().centerEase(loc); that.selectImage(context, node.key, true); } @@ -676,14 +619,11 @@ export default { // This allows images to be selected from places that dont have access // to the full image datum (like the street signs layer or the js viewer) selectImage: function(context, imageKey, fromViewer) { - - _mlySelectedImageKey = imageKey; - this.updateUrlImage(imageKey); - var d = _mlyCache.images.forImageKey[imageKey]; + const d = _mlyCache.images.forImageKey[imageKey]; - var viewer = context.container().select('.photoviewer'); + const viewer = context.container().select('.photoviewer'); if (!viewer.empty()) viewer.datum(d); imageKey = (d && d.key) || imageKey; @@ -691,14 +631,12 @@ export default { _mlyClicks.push(imageKey); } - this.setStyles(context, null, true); - if (_mlyShowFeatureDetections) { - this.updateDetections(imageKey, apibase + 'image_detections?layers=points&values=' + mapFeatureConfig.values + '&image_keys=' + imageKey); + this.updateDetections(imageKey, `${imageDetectionUrl}?layers=points&values=${mapFeatureValues}&image_keys=${imageKey}&client_id=${clientId}`); } if (_mlyShowSignDetections) { - this.updateDetections(imageKey, apibase + 'image_detections?layers=trafficsigns&image_keys=' + imageKey); + this.updateDetections(imageKey, `${imageDetectionUrl}?layers=trafficsigns&image_keys=${imageKey}&client_id=${clientId}`); } if (_mlyViewer && imageKey) { @@ -715,23 +653,14 @@ export default { }, - getSelectedImageKey: function() { - return _mlySelectedImageKey; - }, - - - getSequenceKeyForImageKey: function(imageKey) { - return _mlyCache.sequences.forImageKey[imageKey]; - }, - - setActiveImage: function(node) { if (node) { _mlyActiveImage = { ca: node.originalCA, key: node.key, loc: [node.originalLatLon.lon, node.originalLatLon.lat], - pano: node.pano + pano: node.pano, + sequenceKey: node.sequenceKey }; } else { _mlyActiveImage = null; @@ -754,21 +683,14 @@ export default { .classed('currentView', false); } - var hoveredImageKey = hovered && hovered.key; - var hoveredSequenceKey = hoveredImageKey && this.getSequenceKeyForImageKey(hoveredImageKey); - var hoveredLineString = hoveredSequenceKey && _mlyCache.sequences.lineString[hoveredSequenceKey]; - var hoveredImageKeys = (hoveredLineString && hoveredLineString.properties.coordinateProperties.image_keys) || []; + const hoveredImageKey = hovered && hovered.key; + const hoveredSequenceKey = hovered && hovered.skey; - var selectedImageKey = _mlySelectedImageKey; - var selectedSequenceKey = selectedImageKey && this.getSequenceKeyForImageKey(selectedImageKey); - var selectedLineString = selectedSequenceKey && _mlyCache.sequences.lineString[selectedSequenceKey]; - var selectedImageKeys = (selectedLineString && selectedLineString.properties.coordinateProperties.image_keys) || []; - - // highlight sibling viewfields on either the selected or the hovered sequences - var highlightedImageKeys = utilArrayUnion(hoveredImageKeys, selectedImageKeys); + const selectedImageKey = _mlyActiveImage && _mlyActiveImage.key; + const selectedSequenceKey = _mlyActiveImage && _mlyActiveImage.sequenceKey; context.container().selectAll('.layer-mapillary .viewfield-group') - .classed('highlighted', function(d) { return highlightedImageKeys.indexOf(d.key) !== -1; }) + .classed('highlighted', function(d) { return d.skey === selectedSequenceKey; }) .classed('hovered', function(d) { return d.key === hoveredImageKey; }); context.container().selectAll('.layer-mapillary .sequence') @@ -780,7 +702,7 @@ export default { .attr('d', viewfieldPath); function viewfieldPath() { - var d = this.parentNode.__data__; + const d = this.parentNode.__data__; if (d.pano && d.key !== selectedImageKey) { return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; } else { @@ -806,23 +728,22 @@ export default { } function showDetections(detections) { + const tagComponent = _mlyViewer.getComponent('tag'); detections.forEach(function(data) { - var tag = makeTag(data); + const tag = makeTag(data); if (tag) { - var tagComponent = _mlyViewer.getComponent('tag'); tagComponent.add([tag]); } }); } function makeTag(data) { - var valueParts = data.value.split('--'); + const valueParts = data.value.split('--'); if (!valueParts.length) return; - - var tag; - var text; - var color = 0xffffff; + let tag; + let text; + let color = 0xffffff; if (_mlyHighlightedDetection === data.key) { color = 0xffff00; @@ -836,7 +757,7 @@ export default { } if (data.shape.type === 'Polygon') { - var polygonGeometry = new Mapillary + const polygonGeometry = new Mapillary .TagComponent .PolygonGeometry(data.shape.coordinates[0]); @@ -854,7 +775,7 @@ export default { ); } else if (data.shape.type === 'Point') { - var pointGeometry = new Mapillary + const pointGeometry = new Mapillary .TagComponent .PointGeometry(data.shape.coordinates[0]); @@ -873,8 +794,8 @@ export default { } }, + cache: function() { return _mlyCache; } - }; diff --git a/modules/svg/mapillary_images.js b/modules/svg/mapillary_images.js index 41f0eac0f..379a00003 100644 --- a/modules/svg/mapillary_images.js +++ b/modules/svg/mapillary_images.js @@ -6,13 +6,12 @@ import { services } from '../services'; export function svgMapillaryImages(projection, context, dispatch) { - var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); - var minZoom = 12; - var minMarkerZoom = 16; - var minViewfieldZoom = 18; - var layer = d3_select(null); - var _mapillary; - var viewerCompassAngle; + const throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); + const minZoom = 12; + const minMarkerZoom = 16; + const minViewfieldZoom = 18; + let layer = d3_select(null); + let _mapillary; function init() { @@ -35,7 +34,7 @@ export function svgMapillaryImages(projection, context, dispatch) { function showLayer() { - var service = getService(); + const service = getService(); if (!service) return; editOn(); @@ -72,7 +71,7 @@ export function svgMapillaryImages(projection, context, dispatch) { function click(d3_event, d) { - var service = getService(); + const service = getService(); if (!service) return; service @@ -87,23 +86,22 @@ export function svgMapillaryImages(projection, context, dispatch) { } - function mouseover(d) { - var service = getService(); + function mouseover(d3_event, d) { + const service = getService(); + if (service) service.setStyles(context, d); } function mouseout() { - var service = getService(); + const service = getService(); if (service) service.setStyles(context, null); } function transform(d) { - var t = svgPointTransform(projection)(d); - if (d.pano && viewerCompassAngle !== null && isFinite(viewerCompassAngle)) { - t += ' rotate(' + Math.floor(viewerCompassAngle) + ',0,0)'; - } else if (d.ca) { + let t = svgPointTransform(projection)(d); + if (d.ca) { t += ' rotate(' + Math.floor(d.ca) + ',0,0)'; } return t; @@ -111,11 +109,10 @@ export function svgMapillaryImages(projection, context, dispatch) { function filterImages(images) { - var showsPano = context.photos().showsPanoramic(); - var showsFlat = context.photos().showsFlat(); - var fromDate = context.photos().fromDate(); - var toDate = context.photos().toDate(); - var usernames = context.photos().usernames(); + const showsPano = context.photos().showsPanoramic(); + const showsFlat = context.photos().showsFlat(); + const fromDate = context.photos().fromDate(); + const toDate = context.photos().toDate(); if (!showsPano || !showsFlat) { images = images.filter(function(image) { @@ -124,69 +121,42 @@ export function svgMapillaryImages(projection, context, dispatch) { }); } if (fromDate) { - var fromTimestamp = new Date(fromDate).getTime(); images = images.filter(function(image) { - return new Date(image.captured_at).getTime() >= fromTimestamp; + return new Date(image.captured_at).getTime() >= new Date(fromDate).getTime(); }); } if (toDate) { - var toTimestamp = new Date(toDate).getTime(); images = images.filter(function(image) { - return new Date(image.captured_at).getTime() <= toTimestamp; - }); - } - if (usernames) { - images = images.filter(function(image) { - return usernames.indexOf(image.captured_by) !== -1; + return new Date(image.captured_at).getTime() <= new Date(toDate).getTime(); }); } + return images; } - function filterSequences(sequences, service) { - var showsPano = context.photos().showsPanoramic(); - var showsFlat = context.photos().showsFlat(); - var fromDate = context.photos().fromDate(); - var toDate = context.photos().toDate(); - var usernames = context.photos().usernames(); + function filterSequences(sequences) { + const showsPano = context.photos().showsPanoramic(); + const showsFlat = context.photos().showsFlat(); + const fromDate = context.photos().fromDate(); + const toDate = context.photos().toDate(); if (!showsPano || !showsFlat) { sequences = sequences.filter(function(sequence) { if (sequence.properties.hasOwnProperty('pano')) { if (sequence.properties.pano) return showsPano; return showsFlat; - } else { - // if the sequence doesn't specify pano or not, search its images - var cProps = sequence.properties.coordinateProperties; - if (cProps && cProps.image_keys && cProps.image_keys.length > 0) { - for (var index in cProps.image_keys) { - var imageKey = cProps.image_keys[index]; - var image = service.cachedImage(imageKey); - if (image && image.hasOwnProperty('pano')) { - if (image.pano) return showsPano; - return showsFlat; - } - } - } } return false; }); } if (fromDate) { - var fromTimestamp = new Date(fromDate).getTime(); sequences = sequences.filter(function(sequence) { - return new Date(sequence.properties.captured_at).getTime() >= fromTimestamp; + return new Date(sequence.properties.captured_at).getTime() >= new Date(fromDate).getTime(); }); } if (toDate) { - var toTimestamp = new Date(toDate).getTime(); sequences = sequences.filter(function(sequence) { - return new Date(sequence.properties.captured_at).getTime() <= toTimestamp; - }); - } - if (usernames) { - sequences = sequences.filter(function(sequence) { - return usernames.indexOf(sequence.properties.username) !== -1; + return new Date(sequence.properties.captured_at).getTime() <= new Date(toDate).getTime(); }); } @@ -195,19 +165,19 @@ export function svgMapillaryImages(projection, context, dispatch) { function update() { - var z = ~~context.map().zoom(); - var showMarkers = (z >= minMarkerZoom); - var showViewfields = (z >= minViewfieldZoom); + const z = ~~context.map().zoom(); + const showMarkers = (z >= minMarkerZoom); + const showViewfields = (z >= minViewfieldZoom); - var service = getService(); - var sequences = (service ? service.sequences(projection) : []); - var images = (service && showMarkers ? service.images(projection) : []); + const service = getService(); + let sequences = (service ? service.sequences(projection) : []); + let images = (service && showMarkers ? service.images(projection) : []); images = filterImages(images); sequences = filterSequences(sequences, service); service.filterViewer(context); - var traces = layer.selectAll('.sequences').selectAll('.sequence') + let traces = layer.selectAll('.sequences').selectAll('.sequence') .data(sequences, function(d) { return d.properties.key; }); // exit @@ -222,7 +192,7 @@ export function svgMapillaryImages(projection, context, dispatch) { .attr('d', svgPath(projection).geojson); - var groups = layer.selectAll('.markers').selectAll('.viewfield-group') + const groups = layer.selectAll('.markers').selectAll('.viewfield-group') .data(images, function(d) { return d.key; }); // exit @@ -230,7 +200,7 @@ export function svgMapillaryImages(projection, context, dispatch) { .remove(); // enter - var groupsEnter = groups.enter() + const groupsEnter = groups.enter() .append('g') .attr('class', 'viewfield-group') .on('mouseenter', mouseover) @@ -242,7 +212,7 @@ export function svgMapillaryImages(projection, context, dispatch) { .attr('class', 'viewfield-scale'); // update - var markers = groups + const markers = groups .merge(groupsEnter) .sort(function(a, b) { return b.loc[1] - a.loc[1]; // sort Y @@ -259,7 +229,7 @@ export function svgMapillaryImages(projection, context, dispatch) { .attr('dy', '0') .attr('r', '6'); - var viewfields = markers.selectAll('.viewfield') + const viewfields = markers.selectAll('.viewfield') .data(showViewfields ? [0] : []); viewfields.exit() @@ -273,7 +243,7 @@ export function svgMapillaryImages(projection, context, dispatch) { .attr('d', viewfieldPath); function viewfieldPath() { - var d = this.parentNode.__data__; + const d = this.parentNode.__data__; if (d.pano) { return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; } else { @@ -284,8 +254,8 @@ export function svgMapillaryImages(projection, context, dispatch) { function drawImages(selection) { - var enabled = svgMapillaryImages.enabled; - var service = getService(); + const enabled = svgMapillaryImages.enabled; + const service = getService(); layer = selection.selectAll('.layer-mapillary') .data(service ? [0] : []); @@ -293,7 +263,7 @@ export function svgMapillaryImages(projection, context, dispatch) { layer.exit() .remove(); - var layerEnter = layer.enter() + const layerEnter = layer.enter() .append('g') .attr('class', 'layer-mapillary') .style('display', enabled ? 'block' : 'none'); diff --git a/modules/svg/mapillary_map_features.js b/modules/svg/mapillary_map_features.js index bbd1763db..eb75b002a 100644 --- a/modules/svg/mapillary_map_features.js +++ b/modules/svg/mapillary_map_features.js @@ -5,10 +5,10 @@ import { services } from '../services'; import { t } from '../core/localizer'; export function svgMapillaryMapFeatures(projection, context, dispatch) { - var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); - var minZoom = 12; - var layer = d3_select(null); - var _mapillary; + const throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); + const minZoom = 12; + let layer = d3_select(null); + let _mapillary; function init() { @@ -30,7 +30,7 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) { function showLayer() { - var service = getService(); + const service = getService(); if (!service) return; service.loadObjectResources(context); @@ -56,16 +56,15 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) { function click(d3_event, d) { - var service = getService(); + const service = getService(); if (!service) return; context.map().centerEase(d.loc); - var selectedImageKey = service.getSelectedImageKey(); - var imageKey; - var highlightedDetection; - // Pick one of the images the map feature was detected in, - // preference given to an image already selected. + const selectedImageKey = service.getActiveImage() && service.getActiveImage().key; + let imageKey; + let highlightedDetection; + d.detections.forEach(function(detection) { if (!imageKey || selectedImageKey === detection.image_key) { imageKey = detection.image_key; @@ -90,46 +89,33 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) { function filterData(detectedFeatures) { - var service = getService(); - - var fromDate = context.photos().fromDate(); - var toDate = context.photos().toDate(); - var usernames = context.photos().usernames(); + const fromDate = context.photos().fromDate(); + const toDate = context.photos().toDate(); if (fromDate) { - var fromTimestamp = new Date(fromDate).getTime(); detectedFeatures = detectedFeatures.filter(function(feature) { - return new Date(feature.last_seen_at).getTime() >= fromTimestamp; + return new Date(feature.last_seen_at).getTime() >= new Date(fromDate).getTime(); }); } if (toDate) { - var toTimestamp = new Date(toDate).getTime(); detectedFeatures = detectedFeatures.filter(function(feature) { - return new Date(feature.first_seen_at).getTime() <= toTimestamp; - }); - } - if (usernames && service) { - detectedFeatures = detectedFeatures.filter(function(feature) { - return feature.detections.some(function(detection) { - var imageKey = detection.image_key; - var image = service.cachedImage(imageKey); - return image && usernames.indexOf(image.captured_by) !== -1; - }); + return new Date(feature.first_seen_at).getTime() <= new Date(toDate).getTime(); }); } + return detectedFeatures; } function update() { - var service = getService(); - var data = (service ? service.mapFeatures(projection) : []); + const service = getService(); + let data = (service ? service.mapFeatures(projection) : []); data = filterData(data); - var selectedImageKey = service && service.getSelectedImageKey(); - var transform = svgPointTransform(projection); + const selectedImageKey = service && service.getActiveImage() && service.getActiveImage().key; + const transform = svgPointTransform(projection); - var mapFeatures = layer.selectAll('.icon-map-feature') + const mapFeatures = layer.selectAll('.icon-map-feature') .data(data, function(d) { return d.key; }); // exit @@ -137,7 +123,7 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) { .remove(); // enter - var enter = mapFeatures.enter() + const enter = mapFeatures.enter() .append('g') .attr('class', 'icon-map-feature icon-detected') .on('click', click); @@ -180,10 +166,10 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) { }); }) .sort(function(a, b) { - var aSelected = a.detections.some(function(detection) { + const aSelected = a.detections.some(function(detection) { return detection.image_key === selectedImageKey; }); - var bSelected = b.detections.some(function(detection) { + const bSelected = b.detections.some(function(detection) { return detection.image_key === selectedImageKey; }); if (aSelected === bSelected) { @@ -197,8 +183,8 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) { function drawMapFeatures(selection) { - var enabled = svgMapillaryMapFeatures.enabled; - var service = getService(); + const enabled = svgMapillaryMapFeatures.enabled; + const service = getService(); layer = selection.selectAll('.layer-mapillary-map-features') .data(service ? [0] : []); diff --git a/modules/svg/mapillary_position.js b/modules/svg/mapillary_position.js index 792025860..5351b04be 100644 --- a/modules/svg/mapillary_position.js +++ b/modules/svg/mapillary_position.js @@ -6,12 +6,12 @@ import { services } from '../services'; export function svgMapillaryPosition(projection, context) { - var throttledRedraw = _throttle(function () { update(); }, 1000); - var minZoom = 12; - var minViewfieldZoom = 18; - var layer = d3_select(null); - var _mapillary; - var viewerCompassAngle; + const throttledRedraw = _throttle(function () { update(); }, 1000); + const minZoom = 12; + const minViewfieldZoom = 18; + let layer = d3_select(null); + let _mapillary; + let viewerCompassAngle; function init() { @@ -54,7 +54,7 @@ export function svgMapillaryPosition(projection, context) { function transform(d) { - var t = svgPointTransform(projection)(d); + let t = svgPointTransform(projection)(d); if (d.pano && viewerCompassAngle !== null && isFinite(viewerCompassAngle)) { t += ' rotate(' + Math.floor(viewerCompassAngle) + ',0,0)'; } else if (d.ca) { @@ -65,13 +65,13 @@ export function svgMapillaryPosition(projection, context) { function update() { - var z = ~~context.map().zoom(); - var showViewfields = (z >= minViewfieldZoom); + const z = ~~context.map().zoom(); + const showViewfields = (z >= minViewfieldZoom); - var service = getService(); - var node = service && service.getActiveImage(); + const service = getService(); + const node = service && service.getActiveImage(); - var groups = layer.selectAll('.markers').selectAll('.viewfield-group') + const groups = layer.selectAll('.markers').selectAll('.viewfield-group') .data(node ? [node] : [], function(d) { return d.key; }); // exit @@ -79,7 +79,7 @@ export function svgMapillaryPosition(projection, context) { .remove(); // enter - var groupsEnter = groups.enter() + const groupsEnter = groups.enter() .append('g') .attr('class', 'viewfield-group currentView highlighted'); @@ -89,7 +89,7 @@ export function svgMapillaryPosition(projection, context) { .attr('class', 'viewfield-scale'); // update - var markers = groups + const markers = groups .merge(groupsEnter) .attr('transform', transform) .select('.viewfield-scale'); @@ -103,7 +103,7 @@ export function svgMapillaryPosition(projection, context) { .attr('dy', '0') .attr('r', '6'); - var viewfields = markers.selectAll('.viewfield') + const viewfields = markers.selectAll('.viewfield') .data(showViewfields ? [0] : []); viewfields.exit() @@ -117,7 +117,7 @@ export function svgMapillaryPosition(projection, context) { .attr('d', viewfieldPath); function viewfieldPath() { - var d = this.parentNode.__data__; + const d = this.parentNode.__data__; if (d.pano) { return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; } else { @@ -128,7 +128,7 @@ export function svgMapillaryPosition(projection, context) { function drawImages(selection) { - var service = getService(); + const service = getService(); layer = selection.selectAll('.layer-mapillary-position') .data(service ? [0] : []); @@ -136,7 +136,7 @@ export function svgMapillaryPosition(projection, context) { layer.exit() .remove(); - var layerEnter = layer.enter() + const layerEnter = layer.enter() .append('g') .attr('class', 'layer-mapillary-position'); diff --git a/modules/svg/mapillary_signs.js b/modules/svg/mapillary_signs.js index 800424c24..3bbef6125 100644 --- a/modules/svg/mapillary_signs.js +++ b/modules/svg/mapillary_signs.js @@ -5,10 +5,10 @@ import { services } from '../services'; export function svgMapillarySigns(projection, context, dispatch) { - var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); - var minZoom = 12; - var layer = d3_select(null); - var _mapillary; + const throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); + const minZoom = 12; + let layer = d3_select(null); + let _mapillary; function init() { @@ -30,7 +30,7 @@ export function svgMapillarySigns(projection, context, dispatch) { function showLayer() { - var service = getService(); + const service = getService(); if (!service) return; service.loadSignResources(context); @@ -56,16 +56,15 @@ export function svgMapillarySigns(projection, context, dispatch) { function click(d3_event, d) { - var service = getService(); + const service = getService(); if (!service) return; context.map().centerEase(d.loc); - var selectedImageKey = service.getSelectedImageKey(); - var imageKey; - var highlightedDetection; - // Pick one of the images the sign was detected in, - // preference given to an image already selected. + const selectedImageKey = service.getActiveImage() && service.getActiveImage().key; + let imageKey; + let highlightedDetection; + d.detections.forEach(function(detection) { if (!imageKey || selectedImageKey === detection.image_key) { imageKey = detection.image_key; @@ -91,11 +90,8 @@ export function svgMapillarySigns(projection, context, dispatch) { function filterData(detectedFeatures) { - var service = getService(); - var fromDate = context.photos().fromDate(); var toDate = context.photos().toDate(); - var usernames = context.photos().usernames(); if (fromDate) { var fromTimestamp = new Date(fromDate).getTime(); @@ -109,28 +105,20 @@ export function svgMapillarySigns(projection, context, dispatch) { return new Date(feature.first_seen_at).getTime() <= toTimestamp; }); } - if (usernames && service) { - detectedFeatures = detectedFeatures.filter(function(feature) { - return feature.detections.some(function(detection) { - var imageKey = detection.image_key; - var image = service.cachedImage(imageKey); - return image && usernames.indexOf(image.captured_by) !== -1; - }); - }); - } + return detectedFeatures; } function update() { - var service = getService(); - var data = (service ? service.signs(projection) : []); + const service = getService(); + let data = (service ? service.signs(projection) : []); data = filterData(data); - var selectedImageKey = service.getSelectedImageKey(); - var transform = svgPointTransform(projection); + const selectedImageKey = service.getActiveImage() && service.getActiveImage().key; + const transform = svgPointTransform(projection); - var signs = layer.selectAll('.icon-sign') + const signs = layer.selectAll('.icon-sign') .data(data, function(d) { return d.key; }); // exit @@ -138,7 +126,7 @@ export function svgMapillarySigns(projection, context, dispatch) { .remove(); // enter - var enter = signs.enter() + const enter = signs.enter() .append('g') .attr('class', 'icon-sign icon-detected') .on('click', click); @@ -168,10 +156,10 @@ export function svgMapillarySigns(projection, context, dispatch) { }); }) .sort(function(a, b) { - var aSelected = a.detections.some(function(detection) { + const aSelected = a.detections.some(function(detection) { return detection.image_key === selectedImageKey; }); - var bSelected = b.detections.some(function(detection) { + const bSelected = b.detections.some(function(detection) { return detection.image_key === selectedImageKey; }); if (aSelected === bSelected) { @@ -185,8 +173,8 @@ export function svgMapillarySigns(projection, context, dispatch) { function drawSigns(selection) { - var enabled = svgMapillarySigns.enabled; - var service = getService(); + const enabled = svgMapillarySigns.enabled; + const service = getService(); layer = selection.selectAll('.layer-mapillary-signs') .data(service ? [0] : []); diff --git a/test/spec/services/mapillary.js b/test/spec/services/mapillary.js index 357c119d7..7a35bfad3 100644 --- a/test/spec/services/mapillary.js +++ b/test/spec/services/mapillary.js @@ -42,6 +42,7 @@ describe('iD.serviceMapillary', function() { }); }); + describe('#reset', function() { it('resets cache and image', function() { mapillary.cache().foo = 'bar'; @@ -49,227 +50,7 @@ describe('iD.serviceMapillary', function() { mapillary.reset(); expect(mapillary.cache()).to.not.have.property('foo'); - expect(mapillary.getSelectedImageKey()).to.be.null; - }); - }); - - describe('#loadImages', function() { - it('fires loadedImages when images are loaded', function(done) { - var spy = sinon.spy(); - mapillary.on('loadedImages', spy); - - mapillary.loadImages(context.projection); - - var features = [{ - type: 'Feature', - geometry: { type: 'Point', coordinates: [10,0] }, - properties: { ca: 90, key: '0' } - }]; - var response = { type: 'FeatureCollection', features: features }; - - server.respondWith('GET', /images/, - [200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]); - server.respond(); - window.setTimeout(function() { - expect(spy).to.have.been.called; - expect(server.requests().length).to.eql(2); - done(); - }, 500); - }); - - it('does not load images around null island', function(done) { - var spy = sinon.spy(); - context.projection.translate([0,0]); - - mapillary.on('loadedImages', spy); - mapillary.loadImages(context.projection); - - var features = [{ - type: 'Feature', - geometry: { type: 'Point', coordinates: [0,0] }, - properties: { ca: 90, key: '0' } - }]; - var response = { type: 'FeatureCollection', features: features }; - - server.respondWith('GET', /images/, - [200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]); - server.respond(); - - window.setTimeout(function() { - expect(spy).to.have.been.not.called; - expect(server.requests().length).to.eql(0); // no tile requests of any kind - done(); - }, 200); - }); - - it('loads multiple pages of image results', function(done) { - var calls = 0; - mapillary.on('loadedImages', function() { - server.respond(); // respond to new fetches - if (++calls === 2) { - expect(server.requests().length).to.eql(3); // 2 images, 1 sequences - done(); - } - }); - - mapillary.loadImages(context.projection); - - var features0 = []; - var features1 = []; - var i, key; - - for (i = 0; i < 1000; i++) { - key = String(i); - features0.push({ - type: 'Feature', - geometry: { type: 'Point', coordinates: [10,0] }, - properties: { ca: 90, key: key } - }); - } - for (i = 0; i < 500; i++) { - key = String(1000 + i); - features1.push({ - type: 'Feature', - geometry: { type: 'Point', coordinates: [10,0] }, - properties: { ca: 90, key: key } - }); - } - - var response0 = { type: 'FeatureCollection', features: features0 }; - var response1 = { type: 'FeatureCollection', features: features1 }; - - server.respondWith('GET', /\/images\?.*&page=0/, - [200, { 'Content-Type': 'application/json' }, JSON.stringify(response0) ]); - server.respondWith('GET', /\/images\?.*&page=1/, - [200, { 'Content-Type': 'application/json' }, JSON.stringify(response1) ]); - server.respond(); - }); - }); - - - describe('#loadSigns', function() { - it('fires loadedSigns when signs are loaded', function(done) { - var spy = sinon.spy(); - mapillary.on('loadedSigns', spy); - - mapillary.loadSigns(context.projection); - - var detections = [{ detection_key: '0', image_key: '0' }]; - var features = [{ - type: 'Feature', - geometry: { type: 'Point', coordinates: [10,0] }, - properties: { detections: detections, key: '0', value: 'not-in-set' } - }]; - var response = { type: 'FeatureCollection', features: features }; - - server.respondWith('GET', /map_features/, - [200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]); - server.respond(); - - window.setTimeout(function() { - expect(spy).to.have.been.called; - expect(server.requests().length).to.eql(1); - done(); - }, 200); - }); - - it('does not load signs around null island', function(done) { - var spy = sinon.spy(); - context.projection.translate([0,0]); - - mapillary.on('loadedSigns', spy); - mapillary.loadSigns(context.projection); - - var detections = [{ detection_key: '0', image_key: '0' }]; - var features = [{ - type: 'Feature', - geometry: { type: 'Point', coordinates: [0,0] }, - properties: { detections: detections, key: '0', value: 'not-in-set' } - }]; - var response = { type: 'FeatureCollection', features: features }; - - server.respondWith('GET', /map_features/, - [200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]); - server.respond(); - - window.setTimeout(function() { - expect(spy).to.have.been.not.called; - expect(server.requests().length).to.eql(0); // no tile requests of any kind - done(); - }, 200); - }); - - it.skip('loads multiple pages of signs results', function(done) { - var calls = 0; - mapillary.on('loadedSigns', function() { - server.respond(); // respond to new fetches - if (++calls === 2) { - expect(server.requests().length).to.eql(4); // 2 images, 1 map_features, 1 image_detections - done(); - } - }); - - mapillary.loadSigns(context.projection); - - var features0 = []; - var features1 = []; - var i, key, detections; - - for (i = 0; i < 1000; i++) { - key = String(i); - detections = [{ detection_key: key, image_key: key }]; - features0.push({ - type: 'Feature', - geometry: { type: 'Point', coordinates: [10,0] }, - properties: { detections: detections, key: key, value: 'not-in-set' } - }); - } - for (i = 0; i < 500; i++) { - key = String(1000 + i); - detections = [{ detection_key: key, image_key: key }]; - features1.push({ - type: 'Feature', - geometry: { type: 'Point', coordinates: [10,0] }, - properties: { detections: detections, key: key, value: 'not-in-set' } - }); - } - - var response0 = { type: 'FeatureCollection', features: features0 }; - var response1 = { type: 'FeatureCollection', features: features1 }; - - server.respondWith('GET', /\/map_features\?.*&page=0/, - [200, { 'Content-Type': 'application/json' }, JSON.stringify(response0) ]); - server.respondWith('GET', /\/map_features\?.*&page=1/, - [200, { 'Content-Type': 'application/json' }, JSON.stringify(response1) ]); - server.respond(); - }); - }); - - - describe('#loadMapFeatures', function() { - it('fires loadedMapFeatures when map features are loaded', function(done) { - var spy = sinon.spy(); - mapillary.on('loadedMapFeatures', spy); - - mapillary.loadMapFeatures(context.projection); - - var detections = [{ detection_key: '0', image_key: '0' }]; - var features = [{ - type: 'Feature', - geometry: { type: 'Point', coordinates: [10,0] }, - properties: { detections: detections, key: '0', value: 'not-in-set' } - }]; - var response = { type: 'FeatureCollection', features: features }; - - server.respondWith('GET', /map_features/, - [200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]); - server.respond(); - - window.setTimeout(function() { - expect(spy).to.have.been.called; - expect(server.requests().length).to.eql(1); - done(); - }, 500); + expect(mapillary.getActiveImage()).to.be.null; }); }); @@ -353,9 +134,9 @@ describe('iD.serviceMapillary', function() { 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: '0', loc: [10,0], ca: 90 } }, - { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90 } }, - { minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], ca: 90 } } + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90, skey: '-' } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90, skey: '-' } }, + { minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], ca: 90, skey: '-' } } ]; mapillary.cache().images.rtree.load(features); @@ -376,40 +157,24 @@ describe('iD.serviceMapillary', function() { } }; - mapillary.cache().sequences.lineString['-'] = gj; - mapillary.cache().sequences.forImageKey['0'] = '-'; - mapillary.cache().sequences.forImageKey['1'] = '-'; - mapillary.cache().sequences.forImageKey['2'] = '-'; + mapillary.cache().sequences.lineString['-'] = [gj]; var res = mapillary.sequences(context.projection); expect(res).to.deep.eql([gj]); }); }); - describe('#selectImage', function() { + + describe('#setActiveImage', function() { it('gets and sets the selected image', function() { - var d = { key: 'baz', loc: [10,0] }; - mapillary.selectImage(context, d.key); - expect(mapillary.getSelectedImageKey()).to.eql(d.key); + var node = { key: 'baz', originalLatLon: [10,0] }; + mapillary.setActiveImage(node); + expect(mapillary.getActiveImage().key).to.eql(node.key); }); }); - describe('#parsePagination', function() { - it('gets URL for next page of results from API', function() { - var linkHeader = '; rel="first", ; rel="next"'; - var pagination = mapillary.parsePagination(linkHeader); - expect(pagination.first).to.eql('https://a.mapillary.com/v3/images?per_page=1000'); - expect(pagination.next).to.eql('https://a.mapillary.com/v3/images?per_page=1000&_start_key_time=1476610926080'); - }); - }); describe('#filterViewer', function() { - it('filters images by username', function() { - context.photos().setUsernameFilter('mapillary'); - var filter = mapillary.filterViewer(context); - expect(filter.length).to.be.equal(2); - }); - it('filters images by dates', function() { context.photos().setDateFilter('fromDate', '2020-01-01'); context.photos().setDateFilter('toDate', '2021-01-01'); @@ -417,5 +182,4 @@ describe('iD.serviceMapillary', function() { expect(filter.length).to.be.equal(3); }); }); - });