Merge branch 'mapillary-update' into develop

# Conflicts:
#	modules/services/mapillary.js
#	modules/svg/mapillary_map_features.js
#	modules/svg/mapillary_signs.js
This commit is contained in:
Quincy Morgan
2020-09-13 16:34:25 -04:00
9 changed files with 458 additions and 145 deletions

View File

@@ -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;
}

View File

@@ -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 <Polygon|Point>
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;
}

View File

@@ -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) },

View File

@@ -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');

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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 = [

View File

@@ -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;
});
});