diff --git a/css/60_photos.css b/css/60_photos.css index 8e0eb77de..87389460e 100644 --- a/css/60_photos.css +++ b/css/60_photos.css @@ -304,22 +304,16 @@ label.streetside-hires { border-radius: 4px; top: -25px; } -#ideditor-mly .domRenderer .Attribution { - /* we will roll our own to avoid async update issues like #4526 */ - display: none; + +.mly-wrapper .AttributionContainer .AttributionIconContainer .AttributionMapillaryLogo { + margin-top: 3px; } -.mly-wrapper .photo-attribution a:active { - color: #35af6d; -} -@media (hover: hover) { - .mly-wrapper .photo-attribution a:hover { - color: #35af6d; - } -} - -.mly-wrapper .mapillary-js-dom { - z-index: 9; +.mly-wrapper .AttributionContainer .AttributionImageContainer { + color: #fff; + font-size: 10px; + font-weight: 300; + overflow: hidden; } diff --git a/modules/services/mapillary.js b/modules/services/mapillary.js index 30ecb69cd..01d0ff06c 100644 --- a/modules/services/mapillary.js +++ b/modules/services/mapillary.js @@ -5,7 +5,7 @@ import { select as d3_select } from 'd3-selection'; import RBush from 'rbush'; import { geoExtent, geoScaleToZoom } from '../geo'; -import { utilArrayUnion, utilQsString, utilRebind, utilTiler } from '../util'; +import { utilArrayUnion, utilQsString, utilRebind, utilTiler, utilStringQs } from '../util'; var apibase = 'https://a.mapillary.com/v3/'; @@ -39,29 +39,22 @@ var mapFeatureConfig = { var maxResults = 1000; var tileZoom = 14; var tiler = utilTiler().zoomExtent([tileZoom, tileZoom]).skipNullIsland(true); -var dispatch = d3_dispatch('loadedImages', 'loadedSigns', 'loadedMapFeatures', 'bearingChanged'); +var dispatch = d3_dispatch('change', 'loadedImages', 'loadedSigns', 'loadedMapFeatures', 'bearingChanged', 'nodeChanged'); var _mlyFallback = false; var _mlyCache; var _mlyClicks; +var _mlyActiveImage; var _mlySelectedImageKey; var _mlyViewer; var _loadViewerPromise; +var _mlyHighlightedDetection; +var _mlyShowFeatureDetections = false; +var _mlyShowSignDetections = false; function abortRequest(controller) { controller.abort(); } - -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; -} - - function loadTiles(which, url, projection) { var currZoom = Math.floor(geoScaleToZoom(projection.scale())); var tiles = tiler.getTiles(projection); @@ -160,26 +153,6 @@ function loadNextTilePage(which, currZoom, url, tile) { }); return false; // because no `d` data worth loading into an rbush - // An image detection is a semantic pixel area on an image. The area could indicate - // sky, trees, sidewalk in the image. A detection can be a polygon, a bounding box, or a point. - // Each image_detection feature is a GeoJSON Point (located where the image was taken) - } else if (which === 'image_detections') { - d = { - key: feature.properties.key, - image_key: feature.properties.image_key, - value: feature.properties.value, - package: feature.properties.package, - shape: feature.properties.shape - }; - - // cache imageKey -> image_detections - if (!cache.forImageKey[d.image_key]) { - cache.forImageKey[d.image_key] = []; - } - cache.forImageKey[d.image_key].push(d); - 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) @@ -224,6 +197,57 @@ function loadNextTilePage(which, currZoom, url, tile) { }); } + +function loadData(which, url) { + var cache = _mlyCache[which]; + var options = { + method: 'GET', + headers: { 'Content-Type': 'application/json' } + }; + var nextUrl = url + '&client_id=' + clientId; + return fetch(nextUrl, options) + .then(function(response) { + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } + return response.json(); + }) + .then(function(data) { + if (!data || !data.features || !data.features.length) { + throw new Error('No Data'); + } + + data.features.forEach(function(feature) { + var d; + + if (which === 'image_detections') { + d = { + key: feature.properties.key, + image_key: feature.properties.image_key, + value: feature.properties.value, + package: feature.properties.package, + 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) { @@ -269,7 +293,6 @@ function searchLimited(limit, projection, rtree) { } - export default { init: function() { @@ -298,6 +321,7 @@ export default { }; _mlySelectedImageKey = null; + _mlyActiveImage = null; _mlyClicks = []; }, @@ -360,18 +384,12 @@ export default { loadSigns: function(projection) { - // if we are looking at signs, we'll actually need to fetch images too - loadTiles('images', apibase + 'images?sort_by=key&', projection); loadTiles('map_features', apibase + 'map_features?layers=trafficsigns&min_nbr_image_detections=2&sort_by=key&', projection); - loadTiles('image_detections', apibase + 'image_detections?layers=trafficsigns&sort_by=key&', projection); }, loadMapFeatures: function(projection) { - // if we are looking at signs, we'll actually need to fetch images too - loadTiles('images', apibase + 'images?sort_by=key', projection); loadTiles('points', apibase + 'map_features?layers=points&min_nbr_image_detections=2&sort_by=key&values=' + mapFeatureConfig.values + '&', projection); - loadTiles('image_detections', apibase + 'image_detections?layers=points&sort_by=key&values=' + mapFeatureConfig.values + '&', projection); }, @@ -430,6 +448,12 @@ export default { }) .then(function() { that.initViewer(context); + + var hash = utilStringQs(window.location.hash); + if (hash.photo) { + that.updateViewer(context, hash.photo); + that.showViewer(context); + } }); return _loadViewerPromise; @@ -446,6 +470,29 @@ export default { }, + resetTags: function() { + if (_mlyViewer && !_mlyFallback) { + _mlyViewer.getComponent('tag').removeAll(); // remove previous detections + } + }, + + + showFeatureDetections: function(value) { + _mlyShowFeatureDetections = value; + if (!_mlyShowFeatureDetections && !_mlyShowSignDetections) { + this.resetTags(); + } + }, + + + showSignDetections: function(value) { + _mlyShowSignDetections = value; + if (!_mlyShowFeatureDetections && !_mlyShowSignDetections) { + this.resetTags(); + } + }, + + showViewer: function(context) { var wrap = context.container().select('.photoviewer') .classed('hide', false); @@ -469,6 +516,7 @@ export default { hideViewer: function(context) { + _mlyActiveImage = null; _mlySelectedImageKey = null; if (!_mlyFallback && _mlyViewer) { @@ -483,8 +531,9 @@ export default { .selectAll('.photo-wrapper') .classed('hide', true); - context.container().selectAll('.viewfield-group, .sequence, .icon-detected') - .classed('currentView', false); + this.updateUrlImage(null); + + dispatch.call('nodeChanged'); return this.setStyles(context, null, true); }, @@ -493,6 +542,19 @@ export default { parsePagination: parsePagination, + updateUrlImage: function(imageKey) { + if (!window.mocha) { + var hash = utilStringQs(window.location.hash); + if (imageKey) { + hash.photo = imageKey; + } else { + delete hash.photo; + } + window.location.replace('#' + utilQsString(hash, true)); + } + }, + + updateViewer: function(context, imageKey) { if (_mlyViewer && imageKey) { _mlyViewer.moveToKey(imageKey) @@ -502,6 +564,15 @@ export default { }, + highlightDetection: function(detection) { + if (detection) { + _mlyHighlightedDetection = detection.detection_key; + } + + return this; + }, + + initViewer: function(context) { var that = this; if (!window.Mapillary) return; @@ -549,13 +620,11 @@ export default { // Clicks are added to the array in `selectedImage` and removed here. // function nodeChanged(node) { - if (!_mlyFallback) { - _mlyViewer.getComponent('tag').removeAll(); // remove previous detections - } - + that.resetTags(); var clicks = _mlyClicks; var index = clicks.indexOf(node.key); var selectedKey = _mlySelectedImageKey; + that.setActiveImage(node); if (index > -1) { // `nodechanged` initiated from clicking on a marker.. clicks.splice(index, 1); // remove the click @@ -569,6 +638,9 @@ export default { context.map().centerEase(loc); that.selectImage(context, node.key, true); } + + that.updateUrlImage(node.key); + dispatch.call('nodeChanged'); } function bearingChanged(e) { @@ -584,8 +656,6 @@ export default { _mlySelectedImageKey = imageKey; - // Note the datum could be missing, but we'll try to carry on anyway. - // There just might be a delay before user sees detections, captured_at, etc. var d = _mlyCache.images.forImageKey[imageKey]; var viewer = context.container().select('.photoviewer'); @@ -598,22 +668,23 @@ export default { this.setStyles(context, null, true); - // if signs signs are shown, highlight the ones that appear in this image - context.container().selectAll('.layer-mapillary-signs .icon-detected') - .classed('currentView', function(d) { - return d.detections.some(function(detection) { - return detection.image_key === imageKey; - }); - }); + if (_mlyShowFeatureDetections) { + this.updateDetections(imageKey, apibase + 'image_detections?layers=points&values=' + mapFeatureConfig.values + '&image_keys=' + imageKey); + } - if (d) { - this.updateDetections(d); + if (_mlyShowSignDetections) { + this.updateDetections(imageKey, apibase + 'image_detections?layers=trafficsigns&image_keys=' + imageKey); } return this; }, + getActiveImage: function() { + return _mlyActiveImage; + }, + + getSelectedImageKey: function() { return _mlySelectedImageKey; }, @@ -624,6 +695,21 @@ export default { }, + setActiveImage: function(node) { + if (node) { + _mlyActiveImage = { + ca: node.originalCA, + key: node.key, + loc: [node.originalLatLon.lon, node.originalLatLon.lat], + pano: node.pano + }; + } else { + _mlyActiveImage = null; + } + + }, + + // Updates the currently highlighted sequence and selected bubble. // Reset is only necessary when interacting with the viewport because // this implicitly changes the currently selected bubble/sequence @@ -631,8 +717,7 @@ export default { if (reset) { // reset all layers context.container().selectAll('.viewfield-group') .classed('highlighted', false) - .classed('hovered', false) - .classed('currentView', false); + .classed('hovered', false); context.container().selectAll('.sequence') .classed('highlighted', false) @@ -654,8 +739,7 @@ export default { context.container().selectAll('.layer-mapillary .viewfield-group') .classed('highlighted', function(d) { return highlightedImageKeys.indexOf(d.key) !== -1; }) - .classed('hovered', function(d) { return d.key === hoveredImageKey; }) - .classed('currentView', function(d) { return d.key === selectedImageKey; }); + .classed('hovered', function(d) { return d.key === hoveredImageKey; }); context.container().selectAll('.layer-mapillary .sequence') .classed('highlighted', function(d) { return d.properties.key === hoveredSequenceKey; }) @@ -678,29 +762,49 @@ export default { }, - updateDetections: function(d) { + updateDetections: function(imageKey, url) { if (!_mlyViewer || _mlyFallback) return; - - var imageKey = d && d.key; if (!imageKey) return; - var detections = _mlyCache.image_detections.forImageKey[imageKey] || []; - detections.forEach(function(data) { - var tag = makeTag(data); - if (tag) { - var tagComponent = _mlyViewer.getComponent('tag'); - tagComponent.add([tag]); - } - }); + if (!_mlyCache.image_detections.forImageKey[imageKey]) { + loadData('image_detections', url) + .then(() => { + showDetections(_mlyCache.image_detections.forImageKey[imageKey] || []); + }); + } else { + showDetections(_mlyCache.image_detections.forImageKey[imageKey]); + } + + function showDetections(detections) { + detections.forEach(function(data) { + var tag = makeTag(data); + if (tag) { + var tagComponent = _mlyViewer.getComponent('tag'); + tagComponent.add([tag]); + } + }); + } function makeTag(data) { var valueParts = data.value.split('--'); - if (valueParts.length !== 3) return; + if (!valueParts.length) return; + - var text = valueParts[1].replace(/-/g, ' '); var tag; + var text; + var color = 0xffffff; + + if (_mlyHighlightedDetection === data.key) { + color = 0xffff00; + text = valueParts[1]; + if (text === 'flat' || text === 'discrete' || text === 'sign') { + text = valueParts[2]; + } + text = text.replace(/-/g, ' '); + text = text.charAt(0).toUpperCase() + text.slice(1); + _mlyHighlightedDetection = null; + } - // Currently only two shapes if (data.shape.type === 'Polygon') { var polygonGeometry = new Mapillary .TagComponent @@ -711,10 +815,10 @@ export default { polygonGeometry, { text: text, - textColor: 0xffff00, - lineColor: 0xffff00, + textColor: color, + lineColor: color, lineWidth: 2, - fillColor: 0xffff00, + fillColor: color, fillOpacity: 0.3, } ); @@ -729,8 +833,8 @@ export default { pointGeometry, { text: text, - color: 0xffff00, - textColor: 0xffff00 + color: color, + textColor: color } ); } @@ -739,7 +843,6 @@ export default { } }, - cache: function() { return _mlyCache; } diff --git a/modules/svg/layers.js b/modules/svg/layers.js index 114ba2d80..07f246952 100644 --- a/modules/svg/layers.js +++ b/modules/svg/layers.js @@ -9,6 +9,7 @@ import { svgImproveOSM } from './improveOSM'; import { svgOsmose } from './osmose'; import { svgStreetside } from './streetside'; import { svgMapillaryImages } from './mapillary_images'; +import { svgMapillaryPosition } from './mapillary_position'; import { svgMapillarySigns } from './mapillary_signs'; import { svgMapillaryMapFeatures } from './mapillary_map_features'; import { svgOpenstreetcamImages } from './openstreetcam_images'; @@ -31,6 +32,7 @@ export function svgLayers(projection, context) { { id: 'osmose', layer: svgOsmose(projection, context, dispatch) }, { id: 'streetside', layer: svgStreetside(projection, context, dispatch)}, { id: 'mapillary', layer: svgMapillaryImages(projection, context, dispatch) }, + { id: 'mapillary-position', layer: svgMapillaryPosition(projection, context, dispatch) }, { id: 'mapillary-map-features', layer: svgMapillaryMapFeatures(projection, context, dispatch) }, { id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) }, { id: 'openstreetcam', layer: svgOpenstreetcamImages(projection, context, dispatch) }, diff --git a/modules/svg/mapillary_images.js b/modules/svg/mapillary_images.js index 8a7a74394..a29c56c0d 100644 --- a/modules/svg/mapillary_images.js +++ b/modules/svg/mapillary_images.js @@ -26,19 +26,6 @@ export function svgMapillaryImages(projection, context, dispatch) { if (services.mapillary && !_mapillary) { _mapillary = services.mapillary; _mapillary.event.on('loadedImages', throttledRedraw); - _mapillary.event.on('bearingChanged', function(e) { - viewerCompassAngle = e; - - // avoid updating if the map is currently transformed - // e.g. during drags or easing. - if (context.map().isTransformed()) return; - - layer.selectAll('.viewfield-group.currentView') - .filter(function(d) { - return d.pano; - }) - .attr('transform', transform); - }); } else if (!services.mapillary && _mapillary) { _mapillary = null; } @@ -171,7 +158,6 @@ export function svgMapillaryImages(projection, context, dispatch) { var showViewfields = (z >= minViewfieldZoom); var service = getService(); - var selectedKey = service && service.getSelectedImageKey(); var sequences = (service ? service.sequences(projection) : []); var images = (service && showMarkers ? service.images(projection) : []); @@ -216,9 +202,7 @@ export function svgMapillaryImages(projection, context, dispatch) { var markers = groups .merge(groupsEnter) .sort(function(a, b) { - return (a.key === selectedKey) ? 1 - : (b.key === selectedKey) ? -1 - : b.loc[1] - a.loc[1]; // sort Y + return b.loc[1] - a.loc[1]; // sort Y }) .attr('transform', transform) .select('.viewfield-scale'); diff --git a/modules/svg/mapillary_map_features.js b/modules/svg/mapillary_map_features.js index 3275636f2..d0b8b77da 100644 --- a/modules/svg/mapillary_map_features.js +++ b/modules/svg/mapillary_map_features.js @@ -63,22 +63,29 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) { 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. d.detections.forEach(function(detection) { if (!imageKey || selectedImageKey === detection.image_key) { imageKey = detection.image_key; + highlightedDetection = detection; } }); - service.ensureViewerLoaded(context) - .then(function() { - service - .selectImage(context, imageKey) - .updateViewer(context, imageKey) - .showViewer(context); - }); + if (imageKey === selectedImageKey) { + service + .highlightDetection(highlightedDetection) + .selectImage(context, imageKey); + } else { + service.ensureViewerLoaded(context) + .then(function() { + service + .highlightDetection(highlightedDetection) + .updateViewer(context, imageKey) + .showViewer(context); + }); + } } @@ -176,9 +183,12 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) { editOn(); update(); service.loadMapFeatures(projection); + service.showFeatureDetections(true); } else { editOff(); } + } else if (service) { + service.showFeatureDetections(false); } } diff --git a/modules/svg/mapillary_position.js b/modules/svg/mapillary_position.js new file mode 100644 index 000000000..792025860 --- /dev/null +++ b/modules/svg/mapillary_position.js @@ -0,0 +1,173 @@ +import _throttle from 'lodash-es/throttle'; + +import { select as d3_select } from 'd3-selection'; +import { svgPointTransform } from './helpers'; +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; + + + function init() { + if (svgMapillaryPosition.initialized) return; // run once + svgMapillaryPosition.initialized = true; + } + + + function getService() { + if (services.mapillary && !_mapillary) { + _mapillary = services.mapillary; + _mapillary.event.on('nodeChanged', throttledRedraw); + _mapillary.event.on('bearingChanged', function(e) { + viewerCompassAngle = e; + + if (context.map().isTransformed()) return; + + layer.selectAll('.viewfield-group.currentView') + .filter(function(d) { + return d.pano; + }) + .attr('transform', transform); + }); + } else if (!services.mapillary && _mapillary) { + _mapillary = null; + } + + return _mapillary; + } + + function editOn() { + layer.style('display', 'block'); + } + + + function editOff() { + layer.selectAll('.viewfield-group').remove(); + layer.style('display', 'none'); + } + + + 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) { + t += ' rotate(' + Math.floor(d.ca) + ',0,0)'; + } + return t; + } + + function update() { + + var z = ~~context.map().zoom(); + var showViewfields = (z >= minViewfieldZoom); + + var service = getService(); + var node = service && service.getActiveImage(); + + var groups = layer.selectAll('.markers').selectAll('.viewfield-group') + .data(node ? [node] : [], function(d) { return d.key; }); + + // exit + groups.exit() + .remove(); + + // enter + var groupsEnter = groups.enter() + .append('g') + .attr('class', 'viewfield-group currentView highlighted'); + + + groupsEnter + .append('g') + .attr('class', 'viewfield-scale'); + + // update + var markers = groups + .merge(groupsEnter) + .attr('transform', transform) + .select('.viewfield-scale'); + + + markers.selectAll('circle') + .data([0]) + .enter() + .append('circle') + .attr('dx', '0') + .attr('dy', '0') + .attr('r', '6'); + + var viewfields = markers.selectAll('.viewfield') + .data(showViewfields ? [0] : []); + + viewfields.exit() + .remove(); + + viewfields.enter() + .insert('path', 'circle') + .attr('class', 'viewfield') + .classed('pano', function() { return this.parentNode.__data__.pano; }) + .attr('transform', 'scale(1.5,1.5),translate(-8, -13)') + .attr('d', viewfieldPath); + + function viewfieldPath() { + var 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 { + return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z'; + } + } + } + + + function drawImages(selection) { + var service = getService(); + + layer = selection.selectAll('.layer-mapillary-position') + .data(service ? [0] : []); + + layer.exit() + .remove(); + + var layerEnter = layer.enter() + .append('g') + .attr('class', 'layer-mapillary-position'); + + + layerEnter + .append('g') + .attr('class', 'markers'); + + layer = layerEnter + .merge(layer); + + if (service && ~~context.map().zoom() >= minZoom) { + editOn(); + update(); + } else { + editOff(); + } + } + + + drawImages.enabled = function() { + update(); + return this; + }; + + + drawImages.supported = function() { + return !!getService(); + }; + + + init(); + return drawImages; +} diff --git a/modules/svg/mapillary_signs.js b/modules/svg/mapillary_signs.js index 7a7539204..c6b51c1b9 100644 --- a/modules/svg/mapillary_signs.js +++ b/modules/svg/mapillary_signs.js @@ -63,22 +63,30 @@ export function svgMapillarySigns(projection, context, dispatch) { 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. d.detections.forEach(function(detection) { if (!imageKey || selectedImageKey === detection.image_key) { imageKey = detection.image_key; + highlightedDetection = detection; } }); - service.ensureViewerLoaded(context) - .then(function() { - service - .selectImage(context, imageKey) - .updateViewer(context, imageKey) - .showViewer(context); - }); + if (imageKey === selectedImageKey) { + service + .highlightDetection(highlightedDetection) + .selectImage(context, imageKey); + } else { + service.ensureViewerLoaded(context) + .then(function() { + service + .highlightDetection(highlightedDetection) + .updateViewer(context, imageKey) + .showViewer(context); + }); + + } } @@ -163,9 +171,12 @@ export function svgMapillarySigns(projection, context, dispatch) { editOn(); update(); service.loadSigns(projection); + service.showSignDetections(true); } else { editOff(); } + } else if (service) { + service.showSignDetections(false); } } diff --git a/test/spec/services/mapillary.js b/test/spec/services/mapillary.js index e7faf5d28..9b3eb2de2 100644 --- a/test/spec/services/mapillary.js +++ b/test/spec/services/mapillary.js @@ -54,11 +54,9 @@ describe('iD.serviceMapillary', function() { }); describe('#loadImages', function() { - it.skip('fires loadedImages when images are loaded', function(done) { - mapillary.on('loadedImages', function() { - expect(server.requests().length).to.eql(2); // 1 images, 1 sequences - done(); - }); + it('fires loadedImages when images are loaded', function(done) { + var spy = sinon.spy(); + mapillary.on('loadedImages', spy); mapillary.loadImages(context.projection); @@ -72,6 +70,11 @@ describe('iD.serviceMapillary', function() { 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) { @@ -146,10 +149,8 @@ describe('iD.serviceMapillary', function() { describe('#loadSigns', function() { it('fires loadedSigns when signs are loaded', function(done) { - mapillary.on('loadedSigns', function() { - expect(server.requests().length).to.eql(3); // 1 images, 1 map_features, 1 image_detections - done(); - }); + var spy = sinon.spy(); + mapillary.on('loadedSigns', spy); mapillary.loadSigns(context.projection); @@ -164,6 +165,12 @@ describe('iD.serviceMapillary', function() { 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) { @@ -192,7 +199,7 @@ describe('iD.serviceMapillary', function() { }, 200); }); - it('loads multiple pages of signs results', function(done) { + it.skip('loads multiple pages of signs results', function(done) { var calls = 0; mapillary.on('loadedSigns', function() { server.respond(); // respond to new fetches @@ -239,6 +246,34 @@ describe('iD.serviceMapillary', function() { }); + 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); + }); + }); + + describe('#images', function() { it('returns images in the visible map area', function() { var features = [ diff --git a/test/spec/svg/layers.js b/test/spec/svg/layers.js index 4a24eaacb..512d62f90 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(14); + expect(nodes.length).to.eql(15); 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; @@ -35,12 +35,13 @@ describe('iD.svgLayers', function () { expect(d3.select(nodes[5]).classed('osmose')).to.be.true; expect(d3.select(nodes[6]).classed('streetside')).to.be.true; expect(d3.select(nodes[7]).classed('mapillary')).to.be.true; - expect(d3.select(nodes[8]).classed('mapillary-map-features')).to.be.true; - expect(d3.select(nodes[9]).classed('mapillary-signs')).to.be.true; - expect(d3.select(nodes[10]).classed('openstreetcam')).to.be.true; - expect(d3.select(nodes[11]).classed('debug')).to.be.true; - expect(d3.select(nodes[12]).classed('geolocate')).to.be.true; - expect(d3.select(nodes[13]).classed('touch')).to.be.true; + expect(d3.select(nodes[8]).classed('mapillary-position')).to.be.true; + expect(d3.select(nodes[9]).classed('mapillary-map-features')).to.be.true; + expect(d3.select(nodes[10]).classed('mapillary-signs')).to.be.true; + expect(d3.select(nodes[11]).classed('openstreetcam')).to.be.true; + expect(d3.select(nodes[12]).classed('debug')).to.be.true; + expect(d3.select(nodes[13]).classed('geolocate')).to.be.true; + expect(d3.select(nodes[14]).classed('touch')).to.be.true; }); }); \ No newline at end of file