mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-12 16:52:50 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) },
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
173
modules/svg/mapillary_position.js
Normal file
173
modules/svg/mapillary_position.js
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user