mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-12 16:52:50 +00:00
Switch to Mapillary API v4
This commit is contained in:
@@ -305,17 +305,23 @@ label.streetside-hires {
|
||||
top: -25px;
|
||||
}
|
||||
|
||||
.mly-wrapper .AttributionContainer .AttributionIconContainer .AttributionMapillaryLogo {
|
||||
margin-top: 3px;
|
||||
.mly-wrapper .mapillary-attribution-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mly-wrapper .AttributionContainer .AttributionImageContainer {
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 300;
|
||||
overflow: hidden;
|
||||
.mly-wrapper .mapillary-attribution-container .mapillary-attribution-icon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mly-wrapper .mapillary-attribution-container .mapillary-attribution-username {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mly-wrapper .mapillary-attribution-container .mapillary-attribution-date {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* OpenStreetCam viewer */
|
||||
.osc-wrapper {
|
||||
|
||||
2
dist/locales/en.min.json
vendored
2
dist/locales/en.min.json
vendored
File diff suppressed because one or more lines are too long
@@ -128,7 +128,7 @@ export function rendererPhotos(context) {
|
||||
};
|
||||
|
||||
photos.shouldFilterByUsername = function() {
|
||||
return showsLayer('mapillary') || showsLayer('openstreetcam') || showsLayer('streetside');
|
||||
return !showsLayer('mapillary') && showsLayer('openstreetcam') && !showsLayer('streetside');
|
||||
};
|
||||
|
||||
photos.showsPhotoType = function(val) {
|
||||
|
||||
@@ -1,50 +1,30 @@
|
||||
/* global Mapillary:false */
|
||||
/* global mapillary:false */
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { select as d3_select } from 'd3-selection';
|
||||
|
||||
import base64 from 'base64-js';
|
||||
import Protobuf from 'pbf';
|
||||
import RBush from 'rbush';
|
||||
import vt from '@mapbox/vector-tile';
|
||||
import { VectorTile } from '@mapbox/vector-tile';
|
||||
|
||||
import { geoExtent, geoScaleToZoom } from '../geo';
|
||||
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(',');
|
||||
const accessToken = 'MLY|4100327730013843|5bb78b81720791946a9a7b956c57b7cf';
|
||||
const apiUrl = 'https://graph.mapillary.com/';
|
||||
const baseTileUrl = 'https://tiles.mapillary.com/maps/vtp';
|
||||
const mapFeatureTileUrl = `${baseTileUrl}/mly_map_feature_point/2/{z}/{x}/{y}?access_token=${accessToken}`;
|
||||
const tileUrl = `${baseTileUrl}/mly1_public/2/{z}/{x}/{y}?access_token=${accessToken}`;
|
||||
const trafficSignTileUrl = `${baseTileUrl}/mly_map_feature_traffic_sign/2/{z}/{x}/{y}?access_token=${accessToken}`;
|
||||
|
||||
const viewercss = 'mapillary-js/mapillary.min.css';
|
||||
const viewerjs = 'mapillary-js/mapillary.min.js';
|
||||
const viewercss = 'mapillary-js/mapillary.css';
|
||||
const viewerjs = 'mapillary-js/mapillary.js';
|
||||
const minZoom = 14;
|
||||
const dispatch = d3_dispatch('change', 'loadedImages', 'loadedSigns', 'loadedMapFeatures', 'bearingChanged', 'nodeChanged');
|
||||
const dispatch = d3_dispatch('change', 'loadedImages', 'loadedSigns', 'loadedMapFeatures', 'bearingChanged', 'imageChanged');
|
||||
|
||||
let _loadViewerPromise;
|
||||
let _mlyActiveImage;
|
||||
let _mlyCache;
|
||||
let _mlyClicks;
|
||||
let _mlyFallback = false;
|
||||
let _mlyHighlightedDetection;
|
||||
let _mlyShowFeatureDetections = false;
|
||||
@@ -52,11 +32,8 @@ let _mlyShowSignDetections = false;
|
||||
let _mlyViewer;
|
||||
let _mlyViewerFilter = ['all'];
|
||||
|
||||
function abortRequest(controller) {
|
||||
controller.abort();
|
||||
}
|
||||
|
||||
|
||||
// Load all data for the specified type from Mapillary vector tiles
|
||||
function loadTiles(which, url, maxZoom, projection) {
|
||||
const tiler = utilTiler().zoomExtent([minZoom, maxZoom]).skipNullIsland(true);
|
||||
const tiles = tiler.getTiles(projection);
|
||||
@@ -67,6 +44,7 @@ function loadTiles(which, url, maxZoom, projection) {
|
||||
}
|
||||
|
||||
|
||||
// Load all data for the specified type from one vector tile
|
||||
function loadTile(which, url, tile) {
|
||||
const cache = _mlyCache.requests;
|
||||
const tileId = `${tile.id}-${which}`;
|
||||
@@ -94,7 +72,7 @@ function loadTile(which, url, tile) {
|
||||
|
||||
if (which === 'images') {
|
||||
dispatch.call('loadedImages');
|
||||
} else if (which === 'map_features') {
|
||||
} else if (which === 'signs') {
|
||||
dispatch.call('loadedSigns');
|
||||
} else if (which === 'points') {
|
||||
dispatch.call('loadedMapFeatures');
|
||||
@@ -107,8 +85,9 @@ function loadTile(which, url, tile) {
|
||||
}
|
||||
|
||||
|
||||
// Load the data from the vector tile into cache
|
||||
function loadTileDataToCache(data, tile, which) {
|
||||
const vectorTile = new vt.VectorTile(new Protobuf(data));
|
||||
const vectorTile = new VectorTile(new Protobuf(data));
|
||||
let features,
|
||||
cache,
|
||||
layer,
|
||||
@@ -117,24 +96,23 @@ function loadTileDataToCache(data, tile, which) {
|
||||
loc,
|
||||
d;
|
||||
|
||||
if (vectorTile.layers.hasOwnProperty('mapillary-images')) {
|
||||
if (vectorTile.layers.hasOwnProperty('image')) {
|
||||
features = [];
|
||||
cache = _mlyCache.images;
|
||||
layer = vectorTile.layers['mapillary-images'];
|
||||
layer = vectorTile.layers.image;
|
||||
|
||||
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,
|
||||
ca: feature.properties.compass_angle,
|
||||
id: feature.properties.id,
|
||||
is_pano: feature.properties.is_pano,
|
||||
sequence_id: feature.properties.sequence_id,
|
||||
};
|
||||
cache.forImageKey[d.key] = d;
|
||||
cache.forImageId[d.id] = d;
|
||||
features.push({
|
||||
minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d
|
||||
});
|
||||
@@ -144,36 +122,61 @@ function loadTileDataToCache(data, tile, which) {
|
||||
}
|
||||
}
|
||||
|
||||
if (vectorTile.layers.hasOwnProperty('mapillary-sequences')) {
|
||||
if (vectorTile.layers.hasOwnProperty('sequence')) {
|
||||
features = [];
|
||||
cache = _mlyCache.sequences;
|
||||
layer = vectorTile.layers['mapillary-sequences'];
|
||||
layer = vectorTile.layers.sequence;
|
||||
|
||||
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);
|
||||
if (cache.lineString[feature.properties.id]) {
|
||||
cache.lineString[feature.properties.id].push(feature);
|
||||
} else {
|
||||
cache.lineString[feature.properties.key] = [feature];
|
||||
cache.lineString[feature.properties.id] = [feature];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vectorTile.layers.hasOwnProperty('mapillary-map-features')) {
|
||||
if (vectorTile.layers.hasOwnProperty('point')) {
|
||||
features = [];
|
||||
cache = _mlyCache[which];
|
||||
layer = vectorTile.layers['mapillary-map-features'];
|
||||
layer = vectorTile.layers.point;
|
||||
|
||||
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),
|
||||
id: feature.properties.id,
|
||||
first_seen_at: feature.properties.first_seen_at,
|
||||
last_seen_at: feature.properties.last_seen_at
|
||||
last_seen_at: feature.properties.last_seen_at,
|
||||
value: feature.properties.value
|
||||
};
|
||||
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('traffic_sign')) {
|
||||
features = [];
|
||||
cache = _mlyCache[which];
|
||||
layer = vectorTile.layers.point;
|
||||
|
||||
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,
|
||||
id: feature.properties.id,
|
||||
first_seen_at: feature.properties.first_seen_at,
|
||||
last_seen_at: feature.properties.last_seen_at,
|
||||
value: feature.properties.value
|
||||
};
|
||||
features.push({
|
||||
minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d
|
||||
@@ -186,44 +189,25 @@ function loadTileDataToCache(data, tile, which) {
|
||||
}
|
||||
|
||||
|
||||
function loadData(which, url) {
|
||||
const cache = _mlyCache[which];
|
||||
const options = {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
|
||||
return fetch(url, options)
|
||||
// Get data from the API
|
||||
function loadData(url) {
|
||||
return fetch(url)
|
||||
.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');
|
||||
.then(function(result) {
|
||||
if (!result) {
|
||||
return [];
|
||||
}
|
||||
|
||||
data.features.forEach(function(feature) {
|
||||
if (which === 'image_detections') {
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
return result.data || [];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// partition viewport into higher zoom tiles
|
||||
// Partition viewport into higher zoom tiles
|
||||
function partitionViewport(projection) {
|
||||
const z = geoScaleToZoom(projection.scale());
|
||||
const z2 = (Math.ceil(z * 2) / 2) + 2.5; // round to next 0.5 and add 2.5
|
||||
@@ -234,7 +218,7 @@ function partitionViewport(projection) {
|
||||
}
|
||||
|
||||
|
||||
// no more than `limit` results per partition.
|
||||
// Return no more than `limit` results per partition.
|
||||
function searchLimited(limit, projection, rtree) {
|
||||
limit = limit || 5;
|
||||
|
||||
@@ -250,7 +234,7 @@ function searchLimited(limit, projection, rtree) {
|
||||
|
||||
|
||||
export default {
|
||||
// initialize Mapillary
|
||||
// Initialize Mapillary
|
||||
init: function() {
|
||||
if (!_mlyCache) {
|
||||
this.reset();
|
||||
@@ -259,89 +243,92 @@ export default {
|
||||
this.event = utilRebind(this, dispatch, 'on');
|
||||
},
|
||||
|
||||
// reset cache and state
|
||||
// Reset cache and state
|
||||
reset: function() {
|
||||
if (_mlyCache) {
|
||||
Object.values(_mlyCache.requests.inflight).forEach(abortRequest);
|
||||
Object.values(_mlyCache.requests.inflight).forEach(function(request) { request.abort(); });
|
||||
}
|
||||
|
||||
_mlyCache = {
|
||||
images: { rtree: new RBush(), forImageKey: {} },
|
||||
image_detections: { forImageKey: {} },
|
||||
map_features: { rtree: new RBush() },
|
||||
images: { rtree: new RBush(), forImageId: {} },
|
||||
image_detections: { forImageId: {} },
|
||||
signs: { rtree: new RBush() },
|
||||
points: { rtree: new RBush() },
|
||||
sequences: { rtree: new RBush(), lineString: {} },
|
||||
requests: { loaded: {}, inflight: {} }
|
||||
};
|
||||
|
||||
_mlyActiveImage = null;
|
||||
_mlyClicks = [];
|
||||
},
|
||||
|
||||
// get visible images
|
||||
// Get visible images
|
||||
images: function(projection) {
|
||||
const limit = 5;
|
||||
return searchLimited(limit, projection, _mlyCache.images.rtree);
|
||||
},
|
||||
|
||||
/**
|
||||
* get visible traffic signs
|
||||
*/
|
||||
// Get visible traffic signs
|
||||
signs: function(projection) {
|
||||
const limit = 5;
|
||||
return searchLimited(limit, projection, _mlyCache.map_features.rtree);
|
||||
return searchLimited(limit, projection, _mlyCache.signs.rtree);
|
||||
},
|
||||
|
||||
// get visible map (point) features
|
||||
// Get visible map (point) features
|
||||
mapFeatures: function(projection) {
|
||||
const limit = 5;
|
||||
return searchLimited(limit, projection, _mlyCache.points.rtree);
|
||||
},
|
||||
|
||||
// get cached image by key
|
||||
cachedImage: function(imageKey) {
|
||||
return _mlyCache.images.forImageKey[imageKey];
|
||||
// Get cached image by id
|
||||
cachedImage: function(imageId) {
|
||||
return _mlyCache.images.forImageId[imageId];
|
||||
},
|
||||
|
||||
// get visible sequences
|
||||
// Get visible sequences
|
||||
sequences: function(projection) {
|
||||
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 = {};
|
||||
const sequenceIds = {};
|
||||
let lineStrings = [];
|
||||
// find sequences for images in viewport
|
||||
|
||||
_mlyCache.images.rtree.search(bbox)
|
||||
.forEach(function(d) {
|
||||
if (d.data.skey) {
|
||||
sequenceKeys[d.data.skey] = true;
|
||||
if (d.data.sequence_id) {
|
||||
sequenceIds[d.data.sequence_id] = true;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(sequenceKeys).forEach(function(sequenceKey) {
|
||||
lineStrings = lineStrings.concat(_mlyCache.sequences.lineString[sequenceKey]);
|
||||
Object.keys(sequenceIds).forEach(function(sequenceId) {
|
||||
if (_mlyCache.sequences.lineString[sequenceId]) {
|
||||
lineStrings = lineStrings.concat(_mlyCache.sequences.lineString[sequenceId]);
|
||||
}
|
||||
});
|
||||
|
||||
return lineStrings;
|
||||
},
|
||||
|
||||
|
||||
// Load images in the visible area
|
||||
loadImages: function(projection) {
|
||||
loadTiles('images', tileUrl, 14, projection);
|
||||
},
|
||||
|
||||
|
||||
// Load traffic signs in the visible area
|
||||
loadSigns: function(projection) {
|
||||
loadTiles('map_features', `${mapFeatureTileUrl}?layers=trafficsigns&per_page=1000&client_id=${clientId}`, 18, projection);
|
||||
loadTiles('signs', trafficSignTileUrl, 14, projection);
|
||||
},
|
||||
|
||||
|
||||
// Load map (point) features in the visible area
|
||||
loadMapFeatures: function(projection) {
|
||||
loadTiles('points', `${mapFeatureTileUrl}?layers=points&per_page=1000&client_id=${clientId}`, 18, projection);
|
||||
loadTiles('points', mapFeatureTileUrl, 14, projection);
|
||||
},
|
||||
|
||||
|
||||
// Return a promise that resolves when the image viewer (Mapillary JS) library has finished loading
|
||||
ensureViewerLoaded: function(context) {
|
||||
if (_loadViewerPromise) return _loadViewerPromise;
|
||||
|
||||
@@ -362,6 +349,7 @@ export default {
|
||||
let loadedCount = 0;
|
||||
function loaded() {
|
||||
loadedCount += 1;
|
||||
|
||||
// wait until both files are loaded
|
||||
if (loadedCount === 2) resolve();
|
||||
}
|
||||
@@ -406,19 +394,21 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
// Load traffic sign image sprites
|
||||
loadSignResources: function(context) {
|
||||
context.ui().svgDefs.addSprites(['mapillary-sprite'], false /* don't override colors */ );
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
// Load map (point) feature image sprites
|
||||
loadObjectResources: function(context) {
|
||||
context.ui().svgDefs.addSprites(['mapillary-object-sprite'], false /* don't override colors */ );
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
// remove previous detections in viewer
|
||||
// Remove previous detections in image viewer
|
||||
resetTags: function() {
|
||||
if (_mlyViewer && !_mlyFallback) {
|
||||
_mlyViewer.getComponent('tag').removeAll();
|
||||
@@ -426,7 +416,7 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
// show map feature detections in viewer
|
||||
// Show map feature detections in image viewer
|
||||
showFeatureDetections: function(value) {
|
||||
_mlyShowFeatureDetections = value;
|
||||
if (!_mlyShowFeatureDetections && !_mlyShowSignDetections) {
|
||||
@@ -435,7 +425,7 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
// show traffic sign detections in viewer
|
||||
// Show traffic sign detections in image viewer
|
||||
showSignDetections: function(value) {
|
||||
_mlyShowSignDetections = value;
|
||||
if (!_mlyShowFeatureDetections && !_mlyShowSignDetections) {
|
||||
@@ -444,7 +434,7 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
// apply filter to viewer
|
||||
// Apply filter to image viewer
|
||||
filterViewer: function(context) {
|
||||
const showsPano = context.photos().showsPanoramic();
|
||||
const showsFlat = context.photos().showsFlat();
|
||||
@@ -452,7 +442,7 @@ export default {
|
||||
const toDate = context.photos().toDate();
|
||||
const filter = ['all'];
|
||||
|
||||
if (!showsPano) filter.push(['==', 'pano', false]);
|
||||
if (!showsPano) filter.push([ '!=', 'cameraType', 'spherical' ]);
|
||||
if (!showsFlat && showsPano) filter.push(['==', 'pano', true]);
|
||||
if (fromDate) {
|
||||
filter.push(['>=', 'capturedAt', new Date(fromDate).getTime()]);
|
||||
@@ -470,6 +460,7 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
// Make the image viewer visible
|
||||
showViewer: function(context) {
|
||||
const wrap = context.container().select('.photoviewer')
|
||||
.classed('hide', false);
|
||||
@@ -492,6 +483,7 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
// Hide the image viewer and resets map markers
|
||||
hideViewer: function(context) {
|
||||
_mlyActiveImage = null;
|
||||
|
||||
@@ -509,20 +501,20 @@ export default {
|
||||
|
||||
this.updateUrlImage(null);
|
||||
|
||||
dispatch.call('nodeChanged');
|
||||
dispatch.call('imageChanged');
|
||||
dispatch.call('loadedMapFeatures');
|
||||
dispatch.call('loadedSigns');
|
||||
|
||||
return this.setStyles(context, null, true);
|
||||
return this.setStyles(context, null);
|
||||
},
|
||||
|
||||
|
||||
// update the URL with current image key
|
||||
updateUrlImage: function(imageKey) {
|
||||
// Update the URL with current image id
|
||||
updateUrlImage: function(imageId) {
|
||||
if (!window.mocha) {
|
||||
const hash = utilStringQs(window.location.hash);
|
||||
if (imageKey) {
|
||||
hash.photo = 'mapillary/' + imageKey;
|
||||
if (imageId) {
|
||||
hash.photo = 'mapillary/' + imageId;
|
||||
} else {
|
||||
delete hash.photo;
|
||||
}
|
||||
@@ -531,23 +523,23 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
// highlight the detection in the viewer that is related to the clicked map feature
|
||||
// Highlight the detection in the viewer that is related to the clicked map feature
|
||||
highlightDetection: function(detection) {
|
||||
if (detection) {
|
||||
_mlyHighlightedDetection = detection.detection_key;
|
||||
_mlyHighlightedDetection = detection.id;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
// Initialize image viewer (Mapillar JS)
|
||||
initViewer: function(context) {
|
||||
const that = this;
|
||||
if (!window.Mapillary) return;
|
||||
if (!window.mapillary) return;
|
||||
|
||||
const opts = {
|
||||
apiClient: clientId,
|
||||
baseImageSize: 320,
|
||||
accessToken: accessToken,
|
||||
component: {
|
||||
cover: false,
|
||||
keyboard: false,
|
||||
@@ -557,7 +549,7 @@ export default {
|
||||
};
|
||||
|
||||
// Disable components requiring WebGL support
|
||||
if (!Mapillary.isSupported() && Mapillary.isFallbackSupported()) {
|
||||
if (!mapillary.isSupported() && mapillary.isFallbackSupported()) {
|
||||
_mlyFallback = true;
|
||||
opts.component = {
|
||||
cover: false,
|
||||
@@ -572,9 +564,10 @@ export default {
|
||||
};
|
||||
}
|
||||
|
||||
_mlyViewer = new Mapillary.Viewer(opts);
|
||||
_mlyViewer.on('nodechanged', nodeChanged);
|
||||
_mlyViewer.on('bearingchanged', bearingChanged);
|
||||
_mlyViewer = new mapillary.Viewer(opts);
|
||||
_mlyViewer.on('image', imageChanged);
|
||||
_mlyViewer.on('bearing', bearingChanged);
|
||||
|
||||
if (_mlyViewerFilter) {
|
||||
_mlyViewer.setFilter(_mlyViewerFilter);
|
||||
}
|
||||
@@ -584,149 +577,117 @@ export default {
|
||||
if (_mlyViewer) _mlyViewer.resize();
|
||||
});
|
||||
|
||||
// nodeChanged: called after the viewer has changed images and is ready.
|
||||
//
|
||||
// There is some logic here to batch up clicks into a _mlyClicks array
|
||||
// because the user might click on a lot of markers quickly and nodechanged
|
||||
// may be called out of order asynchronously.
|
||||
//
|
||||
// Clicks are added to the array in `selectedImage` and removed here.
|
||||
//
|
||||
function nodeChanged(node) {
|
||||
// imageChanged: called after the viewer has changed images and is ready.
|
||||
function imageChanged(node) {
|
||||
that.resetTags();
|
||||
const clicks = _mlyClicks;
|
||||
const index = clicks.indexOf(node.key);
|
||||
that.setActiveImage(node);
|
||||
that.setStyles(context, null, true);
|
||||
const image = node.image;
|
||||
that.setActiveImage(image);
|
||||
that.setStyles(context, null);
|
||||
const loc = [image.originalLngLat.lng, image.originalLngLat.lat];
|
||||
context.map().centerEase(loc);
|
||||
that.updateUrlImage(image.id);
|
||||
|
||||
if (index > -1) { // `nodechanged` initiated from clicking on a marker..
|
||||
clicks.splice(index, 1); // remove the click
|
||||
} else { // `nodechanged` initiated from the Mapillary viewer controls..
|
||||
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);
|
||||
if (_mlyShowFeatureDetections || _mlyShowSignDetections) {
|
||||
that.updateDetections(image.id, `${apiUrl}/${image.id}/detections?access_token=${accessToken}&fields=id,image,geometry,value`);
|
||||
}
|
||||
dispatch.call('nodeChanged');
|
||||
dispatch.call('imageChanged');
|
||||
}
|
||||
|
||||
|
||||
// bearingChanged: called when the bearing changes in the image viewer.
|
||||
function bearingChanged(e) {
|
||||
dispatch.call('bearingChanged', undefined, e);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Pass in the image key string as `imageKey`.
|
||||
// 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) {
|
||||
this.updateUrlImage(imageKey);
|
||||
|
||||
const d = _mlyCache.images.forImageKey[imageKey];
|
||||
|
||||
const viewer = context.container().select('.photoviewer');
|
||||
if (!viewer.empty()) viewer.datum(d);
|
||||
|
||||
imageKey = (d && d.key) || imageKey;
|
||||
if (!fromViewer && imageKey) {
|
||||
_mlyClicks.push(imageKey);
|
||||
}
|
||||
|
||||
if (_mlyShowFeatureDetections) {
|
||||
this.updateDetections(imageKey, `${imageDetectionUrl}?layers=points&values=${mapFeatureValues}&image_keys=${imageKey}&client_id=${clientId}`);
|
||||
}
|
||||
|
||||
if (_mlyShowSignDetections) {
|
||||
this.updateDetections(imageKey, `${imageDetectionUrl}?layers=trafficsigns&image_keys=${imageKey}&client_id=${clientId}`);
|
||||
}
|
||||
|
||||
if (_mlyViewer && imageKey) {
|
||||
_mlyViewer.moveToKey(imageKey)
|
||||
.catch(function(e) { console.error('mly3', e); }); // eslint-disable-line no-console
|
||||
// Move to an image
|
||||
selectImage: function(context, imageId) {
|
||||
if (_mlyViewer && imageId) {
|
||||
_mlyViewer.moveTo(imageId)
|
||||
.catch(function(e) {
|
||||
console.error('mly3', e); // eslint-disable-line no-console
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
// Return the currently displayed image
|
||||
getActiveImage: function() {
|
||||
return _mlyActiveImage;
|
||||
},
|
||||
|
||||
|
||||
setActiveImage: function(node) {
|
||||
if (node) {
|
||||
// Return a list of detection objects for the given id
|
||||
getDetections: function(id) {
|
||||
return loadData(`${apiUrl}/${id}/detections?access_token=${accessToken}&fields=id,value,image`);
|
||||
},
|
||||
|
||||
|
||||
// Set the currently visible image
|
||||
setActiveImage: function(image) {
|
||||
if (image) {
|
||||
_mlyActiveImage = {
|
||||
ca: node.originalCA,
|
||||
key: node.key,
|
||||
loc: [node.originalLatLon.lon, node.originalLatLon.lat],
|
||||
pano: node.pano,
|
||||
sequenceKey: node.sequenceKey
|
||||
ca: image.originalCompassAngle,
|
||||
id: image.id,
|
||||
loc: [image.originalLngLat.lng, image.originalLngLat.lat],
|
||||
is_pano: image.cameraType === 'spherical',
|
||||
sequence_id: image.sequenceId
|
||||
};
|
||||
} 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
|
||||
setStyles: function(context, hovered, reset) {
|
||||
if (reset) { // reset all layers
|
||||
context.container().selectAll('.viewfield-group')
|
||||
.classed('highlighted', false)
|
||||
.classed('hovered', false);
|
||||
|
||||
context.container().selectAll('.sequence')
|
||||
.classed('highlighted', false)
|
||||
.classed('currentView', false);
|
||||
}
|
||||
|
||||
const hoveredImageKey = hovered && hovered.key;
|
||||
const hoveredSequenceKey = hovered && hovered.skey;
|
||||
|
||||
const selectedImageKey = _mlyActiveImage && _mlyActiveImage.key;
|
||||
const selectedSequenceKey = _mlyActiveImage && _mlyActiveImage.sequenceKey;
|
||||
// Update the currently highlighted sequence and selected bubble.
|
||||
setStyles: function(context, hovered) {
|
||||
const hoveredImageId = hovered && hovered.id;
|
||||
const hoveredSequenceId = hovered && hovered.sequence_id;
|
||||
const selectedSequenceId = _mlyActiveImage && _mlyActiveImage.sequence_id;
|
||||
|
||||
context.container().selectAll('.layer-mapillary .viewfield-group')
|
||||
.classed('highlighted', function(d) { return d.skey === selectedSequenceKey; })
|
||||
.classed('hovered', function(d) { return d.key === hoveredImageKey; });
|
||||
.classed('highlighted', function(d) { return (d.sequence_id === selectedSequenceId) || (d.id === hoveredImageId); })
|
||||
.classed('hovered', function(d) { return d.id === hoveredImageId; });
|
||||
|
||||
context.container().selectAll('.layer-mapillary .sequence')
|
||||
.classed('highlighted', function(d) { return d.properties.key === hoveredSequenceKey; })
|
||||
.classed('currentView', function(d) { return d.properties.key === selectedSequenceKey; });
|
||||
|
||||
// update viewfields if needed
|
||||
context.container().selectAll('.viewfield-group .viewfield')
|
||||
.attr('d', viewfieldPath);
|
||||
|
||||
function viewfieldPath() {
|
||||
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 {
|
||||
return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z';
|
||||
}
|
||||
}
|
||||
.classed('highlighted', function(d) { return d.properties.id === hoveredSequenceId; })
|
||||
.classed('currentView', function(d) { return d.properties.id === selectedSequenceId; });
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
updateDetections: function(imageKey, url) {
|
||||
// Get detections for the current image and shows them in the image viewer
|
||||
updateDetections: function(imageId, url) {
|
||||
if (!_mlyViewer || _mlyFallback) return;
|
||||
if (!imageKey) return;
|
||||
|
||||
if (!_mlyCache.image_detections.forImageKey[imageKey]) {
|
||||
loadData('image_detections', url)
|
||||
.then(() => {
|
||||
showDetections(_mlyCache.image_detections.forImageKey[imageKey] || []);
|
||||
});
|
||||
if (!imageId) return;
|
||||
const cache = _mlyCache.image_detections;
|
||||
if (cache.forImageId[imageId]) {
|
||||
showDetections(_mlyCache.image_detections.forImageId[imageId]);
|
||||
} else {
|
||||
showDetections(_mlyCache.image_detections.forImageKey[imageKey]);
|
||||
loadData(url)
|
||||
.then(detections => {
|
||||
detections.forEach(function(detection) {
|
||||
if (!cache.forImageId[imageId]) {
|
||||
cache.forImageId[imageId] = [];
|
||||
}
|
||||
cache.forImageId[imageId].push({
|
||||
geometry: detection.geometry,
|
||||
id: detection.id,
|
||||
image_id: imageId,
|
||||
value:detection.value
|
||||
});
|
||||
});
|
||||
|
||||
showDetections(_mlyCache.image_detections.forImageId[imageId] || []);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Create a tag for each detection and shows it in the image viewer
|
||||
function showDetections(detections) {
|
||||
const tagComponent = _mlyViewer.getComponent('tag');
|
||||
detections.forEach(function(data) {
|
||||
@@ -737,6 +698,8 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Create a Mapillary JS tag object
|
||||
function makeTag(data) {
|
||||
const valueParts = data.value.split('--');
|
||||
if (!valueParts.length) return;
|
||||
@@ -745,7 +708,7 @@ export default {
|
||||
let text;
|
||||
let color = 0xffffff;
|
||||
|
||||
if (_mlyHighlightedDetection === data.key) {
|
||||
if (_mlyHighlightedDetection === data.id) {
|
||||
color = 0xffff00;
|
||||
text = valueParts[1];
|
||||
if (text === 'flat' || text === 'discrete' || text === 'sign') {
|
||||
@@ -756,45 +719,35 @@ export default {
|
||||
_mlyHighlightedDetection = null;
|
||||
}
|
||||
|
||||
if (data.shape.type === 'Polygon') {
|
||||
const polygonGeometry = new Mapillary
|
||||
.TagComponent
|
||||
.PolygonGeometry(data.shape.coordinates[0]);
|
||||
const geometry = base64.toByteArray(data.geometry);
|
||||
const tile = new VectorTile(new Protobuf(geometry));
|
||||
const layer = tile.layers['mpy-or'];
|
||||
|
||||
tag = new Mapillary.TagComponent.OutlineTag(
|
||||
data.key,
|
||||
polygonGeometry,
|
||||
{
|
||||
text: text,
|
||||
textColor: color,
|
||||
lineColor: color,
|
||||
lineWidth: 2,
|
||||
fillColor: color,
|
||||
fillOpacity: 0.3,
|
||||
}
|
||||
);
|
||||
const geometries = layer.feature(0).loadGeometry();
|
||||
|
||||
} else if (data.shape.type === 'Point') {
|
||||
const pointGeometry = new Mapillary
|
||||
.TagComponent
|
||||
.PointGeometry(data.shape.coordinates[0]);
|
||||
const polygon = geometries.map(ring =>
|
||||
ring.map(point =>
|
||||
[point.x / layer.extent, point.y / layer.extent]));
|
||||
|
||||
tag = new Mapillary.TagComponent.SpotTag(
|
||||
data.key,
|
||||
pointGeometry,
|
||||
{
|
||||
text: text,
|
||||
color: color,
|
||||
textColor: color
|
||||
}
|
||||
);
|
||||
}
|
||||
tag = new mapillary.OutlineTag(
|
||||
data.id,
|
||||
new mapillary.PolygonGeometry(polygon[0]),
|
||||
{
|
||||
text: text,
|
||||
textColor: color,
|
||||
lineColor: color,
|
||||
lineWidth: 2,
|
||||
fillColor: color,
|
||||
fillOpacity: 0.3,
|
||||
}
|
||||
);
|
||||
|
||||
return tag;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Return the current cache
|
||||
cache: function() {
|
||||
return _mlyCache;
|
||||
}
|
||||
|
||||
@@ -544,7 +544,7 @@ export default {
|
||||
.classed('currentView', function(d) { return d.properties.key === selectedSequenceKey; });
|
||||
|
||||
// update viewfields if needed
|
||||
context.container().selectAll('.viewfield-group .viewfield')
|
||||
context.container().selectAll('.layer-openstreetcam .viewfield-group .viewfield')
|
||||
.attr('d', viewfieldPath);
|
||||
|
||||
function viewfieldPath() {
|
||||
|
||||
@@ -942,7 +942,7 @@ export default {
|
||||
.classed('currentView', d => d.properties.key === selectedSequenceKey);
|
||||
|
||||
// update viewfields if needed
|
||||
context.container().selectAll('.viewfield-group .viewfield')
|
||||
context.container().selectAll('.layer-streetside-images .viewfield-group .viewfield')
|
||||
.attr('d', viewfieldPath);
|
||||
|
||||
function viewfieldPath() {
|
||||
|
||||
@@ -70,7 +70,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
|
||||
}
|
||||
|
||||
|
||||
function click(d3_event, d) {
|
||||
function click(d3_event, image) {
|
||||
const service = getService();
|
||||
if (!service) return;
|
||||
|
||||
@@ -78,18 +78,18 @@ export function svgMapillaryImages(projection, context, dispatch) {
|
||||
.ensureViewerLoaded(context)
|
||||
.then(function() {
|
||||
service
|
||||
.selectImage(context, d.key)
|
||||
.selectImage(context, image.id)
|
||||
.showViewer(context);
|
||||
});
|
||||
|
||||
context.map().centerEase(d.loc);
|
||||
context.map().centerEase(image.loc);
|
||||
}
|
||||
|
||||
|
||||
function mouseover(d3_event, d) {
|
||||
function mouseover(d3_event, image) {
|
||||
const service = getService();
|
||||
|
||||
if (service) service.setStyles(context, d);
|
||||
if (service) service.setStyles(context, image);
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
|
||||
|
||||
if (!showsPano || !showsFlat) {
|
||||
images = images.filter(function(image) {
|
||||
if (image.pano) return showsPano;
|
||||
if (image.is_pano) return showsPano;
|
||||
return showsFlat;
|
||||
});
|
||||
}
|
||||
@@ -142,8 +142,8 @@ export function svgMapillaryImages(projection, context, dispatch) {
|
||||
|
||||
if (!showsPano || !showsFlat) {
|
||||
sequences = sequences.filter(function(sequence) {
|
||||
if (sequence.properties.hasOwnProperty('pano')) {
|
||||
if (sequence.properties.pano) return showsPano;
|
||||
if (sequence.properties.hasOwnProperty('is_pano')) {
|
||||
if (sequence.properties.is_pano) return showsPano;
|
||||
return showsFlat;
|
||||
}
|
||||
return false;
|
||||
@@ -151,12 +151,12 @@ export function svgMapillaryImages(projection, context, dispatch) {
|
||||
}
|
||||
if (fromDate) {
|
||||
sequences = sequences.filter(function(sequence) {
|
||||
return new Date(sequence.properties.captured_at).getTime() >= new Date(fromDate).getTime();
|
||||
return new Date(sequence.properties.captured_at).getTime() >= new Date(fromDate).getTime().toString();
|
||||
});
|
||||
}
|
||||
if (toDate) {
|
||||
sequences = sequences.filter(function(sequence) {
|
||||
return new Date(sequence.properties.captured_at).getTime() <= new Date(toDate).getTime();
|
||||
return new Date(sequence.properties.captured_at).getTime() <= new Date(toDate).getTime().toString();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,10 +175,11 @@ export function svgMapillaryImages(projection, context, dispatch) {
|
||||
|
||||
images = filterImages(images);
|
||||
sequences = filterSequences(sequences, service);
|
||||
|
||||
service.filterViewer(context);
|
||||
|
||||
let traces = layer.selectAll('.sequences').selectAll('.sequence')
|
||||
.data(sequences, function(d) { return d.properties.key; });
|
||||
.data(sequences, function(d) { return d.properties.id; });
|
||||
|
||||
// exit
|
||||
traces.exit()
|
||||
@@ -193,7 +194,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
|
||||
|
||||
|
||||
const groups = layer.selectAll('.markers').selectAll('.viewfield-group')
|
||||
.data(images, function(d) { return d.key; });
|
||||
.data(images, function(d) { return d.id; });
|
||||
|
||||
// exit
|
||||
groups.exit()
|
||||
@@ -238,13 +239,12 @@ export function svgMapillaryImages(projection, context, dispatch) {
|
||||
viewfields.enter() // viewfields may or may not be drawn...
|
||||
.insert('path', 'circle') // but if they are, draw below the circles
|
||||
.attr('class', 'viewfield')
|
||||
.classed('pano', function() { return this.parentNode.__data__.pano; })
|
||||
.classed('pano', function() { return this.parentNode.__data__.is_pano; })
|
||||
.attr('transform', 'scale(1.5,1.5),translate(-8, -13)')
|
||||
.attr('d', viewfieldPath);
|
||||
|
||||
function viewfieldPath() {
|
||||
const d = this.parentNode.__data__;
|
||||
if (d.pano) {
|
||||
if (this.parentNode.__data__.is_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';
|
||||
|
||||
@@ -61,30 +61,26 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) {
|
||||
|
||||
context.map().centerEase(d.loc);
|
||||
|
||||
const selectedImageKey = service.getActiveImage() && service.getActiveImage().key;
|
||||
let imageKey;
|
||||
let highlightedDetection;
|
||||
const selectedImageId = service.getActiveImage() && service.getActiveImage().id;
|
||||
|
||||
d.detections.forEach(function(detection) {
|
||||
if (!imageKey || selectedImageKey === detection.image_key) {
|
||||
imageKey = detection.image_key;
|
||||
highlightedDetection = detection;
|
||||
service.getDetections(d.id).then(detections => {
|
||||
if (detections.length) {
|
||||
const imageId = detections[0].image.id;
|
||||
if (imageId === selectedImageId) {
|
||||
service
|
||||
.highlightDetection(detections[0])
|
||||
.selectImage(context, imageId);
|
||||
} else {
|
||||
service.ensureViewerLoaded(context)
|
||||
.then(function() {
|
||||
service
|
||||
.highlightDetection(detections[0])
|
||||
.selectImage(context, imageId)
|
||||
.showViewer(context);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (imageKey === selectedImageKey) {
|
||||
service
|
||||
.highlightDetection(highlightedDetection)
|
||||
.selectImage(context, imageKey);
|
||||
} else {
|
||||
service.ensureViewerLoaded(context)
|
||||
.then(function() {
|
||||
service
|
||||
.highlightDetection(highlightedDetection)
|
||||
.selectImage(context, imageKey)
|
||||
.showViewer(context);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,11 +108,10 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) {
|
||||
let data = (service ? service.mapFeatures(projection) : []);
|
||||
data = filterData(data);
|
||||
|
||||
const selectedImageKey = service && service.getActiveImage() && service.getActiveImage().key;
|
||||
const transform = svgPointTransform(projection);
|
||||
|
||||
const mapFeatures = layer.selectAll('.icon-map-feature')
|
||||
.data(data, function(d) { return d.key; });
|
||||
.data(data, function(d) { return d.id; });
|
||||
|
||||
// exit
|
||||
mapFeatures.exit()
|
||||
@@ -159,26 +154,7 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) {
|
||||
// update
|
||||
mapFeatures
|
||||
.merge(enter)
|
||||
.attr('transform', transform)
|
||||
.classed('currentView', function(d) {
|
||||
return d.detections.some(function(detection) {
|
||||
return detection.image_key === selectedImageKey;
|
||||
});
|
||||
})
|
||||
.sort(function(a, b) {
|
||||
const aSelected = a.detections.some(function(detection) {
|
||||
return detection.image_key === selectedImageKey;
|
||||
});
|
||||
const bSelected = b.detections.some(function(detection) {
|
||||
return detection.image_key === selectedImageKey;
|
||||
});
|
||||
if (aSelected === bSelected) {
|
||||
return b.loc[1] - a.loc[1]; // sort Y
|
||||
} else if (aSelected) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
});
|
||||
.attr('transform', transform);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,15 +23,15 @@ export function svgMapillaryPosition(projection, context) {
|
||||
function getService() {
|
||||
if (services.mapillary && !_mapillary) {
|
||||
_mapillary = services.mapillary;
|
||||
_mapillary.event.on('nodeChanged', throttledRedraw);
|
||||
_mapillary.event.on('imageChanged', throttledRedraw);
|
||||
_mapillary.event.on('bearingChanged', function(e) {
|
||||
viewerCompassAngle = e;
|
||||
viewerCompassAngle = e.bearing;
|
||||
|
||||
if (context.map().isTransformed()) return;
|
||||
|
||||
layer.selectAll('.viewfield-group.currentView')
|
||||
.filter(function(d) {
|
||||
return d.pano;
|
||||
return d.is_pano;
|
||||
})
|
||||
.attr('transform', transform);
|
||||
});
|
||||
@@ -55,7 +55,7 @@ export function svgMapillaryPosition(projection, context) {
|
||||
|
||||
function transform(d) {
|
||||
let t = svgPointTransform(projection)(d);
|
||||
if (d.pano && viewerCompassAngle !== null && isFinite(viewerCompassAngle)) {
|
||||
if (d.is_pano && viewerCompassAngle !== null && isFinite(viewerCompassAngle)) {
|
||||
t += ' rotate(' + Math.floor(viewerCompassAngle) + ',0,0)';
|
||||
} else if (d.ca) {
|
||||
t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
|
||||
@@ -69,10 +69,10 @@ export function svgMapillaryPosition(projection, context) {
|
||||
const showViewfields = (z >= minViewfieldZoom);
|
||||
|
||||
const service = getService();
|
||||
const node = service && service.getActiveImage();
|
||||
const image = service && service.getActiveImage();
|
||||
|
||||
const groups = layer.selectAll('.markers').selectAll('.viewfield-group')
|
||||
.data(node ? [node] : [], function(d) { return d.key; });
|
||||
.data(image ? [image] : [], function(d) { return d.id; });
|
||||
|
||||
// exit
|
||||
groups.exit()
|
||||
@@ -112,18 +112,8 @@ export function svgMapillaryPosition(projection, context) {
|
||||
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() {
|
||||
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 {
|
||||
return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z';
|
||||
}
|
||||
}
|
||||
.attr('d', 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z');
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -61,31 +61,26 @@ export function svgMapillarySigns(projection, context, dispatch) {
|
||||
|
||||
context.map().centerEase(d.loc);
|
||||
|
||||
const selectedImageKey = service.getActiveImage() && service.getActiveImage().key;
|
||||
let imageKey;
|
||||
let highlightedDetection;
|
||||
const selectedImageId = service.getActiveImage() && service.getActiveImage().id;
|
||||
|
||||
d.detections.forEach(function(detection) {
|
||||
if (!imageKey || selectedImageKey === detection.image_key) {
|
||||
imageKey = detection.image_key;
|
||||
highlightedDetection = detection;
|
||||
service.getDetections(d.id).then(detections => {
|
||||
if (detections.length) {
|
||||
const imageId = detections[0].image.id;
|
||||
if (imageId === selectedImageId) {
|
||||
service
|
||||
.highlightDetection(detections[0])
|
||||
.selectImage(context, imageId);
|
||||
} else {
|
||||
service.ensureViewerLoaded(context)
|
||||
.then(function() {
|
||||
service
|
||||
.highlightDetection(detections[0])
|
||||
.selectImage(context, imageId)
|
||||
.showViewer(context);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (imageKey === selectedImageKey) {
|
||||
service
|
||||
.highlightDetection(highlightedDetection)
|
||||
.selectImage(context, imageKey);
|
||||
} else {
|
||||
service.ensureViewerLoaded(context)
|
||||
.then(function() {
|
||||
service
|
||||
.highlightDetection(highlightedDetection)
|
||||
.selectImage(context, imageKey)
|
||||
.showViewer(context);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,11 +110,10 @@ export function svgMapillarySigns(projection, context, dispatch) {
|
||||
let data = (service ? service.signs(projection) : []);
|
||||
data = filterData(data);
|
||||
|
||||
const selectedImageKey = service.getActiveImage() && service.getActiveImage().key;
|
||||
const transform = svgPointTransform(projection);
|
||||
|
||||
const signs = layer.selectAll('.icon-sign')
|
||||
.data(data, function(d) { return d.key; });
|
||||
.data(data, function(d) { return d.id; });
|
||||
|
||||
// exit
|
||||
signs.exit()
|
||||
@@ -149,26 +143,7 @@ export function svgMapillarySigns(projection, context, dispatch) {
|
||||
// update
|
||||
signs
|
||||
.merge(enter)
|
||||
.attr('transform', transform)
|
||||
.classed('currentView', function(d) {
|
||||
return d.detections.some(function(detection) {
|
||||
return detection.image_key === selectedImageKey;
|
||||
});
|
||||
})
|
||||
.sort(function(a, b) {
|
||||
const aSelected = a.detections.some(function(detection) {
|
||||
return detection.image_key === selectedImageKey;
|
||||
});
|
||||
const bSelected = b.detections.some(function(detection) {
|
||||
return detection.image_key === selectedImageKey;
|
||||
});
|
||||
if (aSelected === bSelected) {
|
||||
return b.loc[1] - a.loc[1]; // sort Y
|
||||
} else if (aSelected) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
});
|
||||
.attr('transform', transform);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ export function uiPhotoviewer(context) {
|
||||
.append('button')
|
||||
.attr('class', 'thumb-hide')
|
||||
.on('click', function () {
|
||||
if (services.streetside) { services.streetside.hideViewer(context); }
|
||||
//if (services.streetside) { services.streetside.hideViewer(context); }
|
||||
if (services.mapillary) { services.mapillary.hideViewer(context); }
|
||||
if (services.openstreetcam) { services.openstreetcam.hideViewer(context); }
|
||||
//if (services.openstreetcam) { services.openstreetcam.hideViewer(context); }
|
||||
})
|
||||
.append('div')
|
||||
.call(svgIcon('#iD-icon-close'));
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"abortcontroller-polyfill": "^1.4.0",
|
||||
"aes-js": "^3.1.2",
|
||||
"alif-toolkit": "^1.2.9",
|
||||
"base64-js": "1.5.1",
|
||||
"core-js": "^3.6.5",
|
||||
"diacritics": "1.3.0",
|
||||
"fast-deep-equal": "~3.1.1",
|
||||
@@ -94,7 +95,7 @@
|
||||
"happen": "^0.3.1",
|
||||
"js-yaml": "^4.0.0",
|
||||
"json-stringify-pretty-compact": "^3.0.0",
|
||||
"mapillary-js": "~3.1.0",
|
||||
"mapillary-js": "4.0.0",
|
||||
"mapillary_sprite_source": "^1.8.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mocha": "^7.0.1",
|
||||
|
||||
@@ -33,7 +33,8 @@ describe('iD.serviceMapillary', function() {
|
||||
var cache = mapillary.cache();
|
||||
expect(cache).to.have.property('images');
|
||||
expect(cache).to.have.property('image_detections');
|
||||
expect(cache).to.have.property('map_features');
|
||||
expect(cache).to.have.property('points');
|
||||
expect(cache).to.have.property('signs');
|
||||
expect(cache).to.have.property('sequences');
|
||||
|
||||
mapillary.init();
|
||||
@@ -91,26 +92,56 @@ describe('iD.serviceMapillary', function() {
|
||||
|
||||
describe('#signs', function() {
|
||||
it('returns signs in the visible map area', function() {
|
||||
var detections = [{
|
||||
detection_key: '78vqha63gs1upg15s823qckcmn',
|
||||
image_key: 'bwYs-uXLDvm_meo_EC5Nzw'
|
||||
}];
|
||||
var features = [
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], detections: detections } },
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], detections: detections } },
|
||||
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], detections: detections } }
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0] } },
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0] } },
|
||||
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1] } }
|
||||
];
|
||||
|
||||
mapillary.cache().map_features.rtree.load(features);
|
||||
mapillary.cache().signs.rtree.load(features);
|
||||
var res = mapillary.signs(context.projection);
|
||||
|
||||
expect(res).to.deep.eql([
|
||||
{ key: '0', loc: [10,0], detections: detections },
|
||||
{ key: '1', loc: [10,0], detections: detections }
|
||||
{ key: '0', loc: [10,0] },
|
||||
{ key: '1', loc: [10,0] }
|
||||
]);
|
||||
});
|
||||
|
||||
it('limits results no more than 5 stacked signs in one spot', function() {
|
||||
var features = [
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0] } },
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0] } },
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '2', loc: [10,0] } },
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '3', loc: [10,0] } },
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0] } },
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '5', loc: [10,0] } }
|
||||
];
|
||||
|
||||
mapillary.cache().signs.rtree.load(features);
|
||||
var res = mapillary.signs(context.projection);
|
||||
expect(res).to.have.length.of.at.most(5);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('#mapFeatures', function() {
|
||||
it('returns map features in the visible map area', function() {
|
||||
var features = [
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0] } },
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0] } },
|
||||
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1] } }
|
||||
];
|
||||
|
||||
mapillary.cache().points.rtree.load(features);
|
||||
var res = mapillary.mapFeatures(context.projection);
|
||||
|
||||
expect(res).to.deep.eql([
|
||||
{ key: '0', loc: [10,0] },
|
||||
{ key: '1', loc: [10,0] }
|
||||
]);
|
||||
});
|
||||
|
||||
it('limits results no more than 5 stacked map features in one spot', function() {
|
||||
var detections = [{
|
||||
detection_key: '78vqha63gs1upg15s823qckcmn',
|
||||
image_key: 'bwYs-uXLDvm_meo_EC5Nzw'
|
||||
@@ -124,8 +155,8 @@ describe('iD.serviceMapillary', function() {
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '5', loc: [10,0], detections: detections } }
|
||||
];
|
||||
|
||||
mapillary.cache().map_features.rtree.load(features);
|
||||
var res = mapillary.signs(context.projection);
|
||||
mapillary.cache().points.rtree.load(features);
|
||||
var res = mapillary.mapFeatures(context.projection);
|
||||
expect(res).to.have.length.of.at.most(5);
|
||||
});
|
||||
});
|
||||
@@ -134,9 +165,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, 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: '-' } }
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90, sequence_id: '-' } },
|
||||
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90, sequence_id: '-' } },
|
||||
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], ca: 90, sequence_id: '-' } }
|
||||
];
|
||||
|
||||
mapillary.cache().images.rtree.load(features);
|
||||
@@ -167,9 +198,9 @@ describe('iD.serviceMapillary', function() {
|
||||
|
||||
describe('#setActiveImage', function() {
|
||||
it('gets and sets the selected image', function() {
|
||||
var node = { key: 'baz', originalLatLon: [10,0] };
|
||||
var node = { id: 'baz', originalLngLat: {lng: 10, lat: 0}};
|
||||
mapillary.setActiveImage(node);
|
||||
expect(mapillary.getActiveImage().key).to.eql(node.key);
|
||||
expect(mapillary.getActiveImage().id).to.eql(node.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user