Merge pull request #8372 from mapillary/mapillary-vector-tiles

Use Mapillary vector tiles and switch to v4
This commit is contained in:
Milos Brzakovic
2021-06-30 12:21:01 +02:00
committed by GitHub
13 changed files with 611 additions and 1022 deletions
+13 -7
View File
@@ -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 {
+27 -1
View File
@@ -1318,11 +1318,27 @@ en:
title: "Map Features"
tooltip: "Map features from Mapillary"
construction:
barrier:
temporary: temporary barrier
flat:
crosswalk_plain: plain crosswalk
driveway: driveway
marking:
discrete:
crosswalk_zebra: zebra crosswalk
arrow:
left: lane marking - arrow (left)
right: lane marking - arrow (right)
split_left_or_straight: lane marking - arrow (split left or straight)
split_right_or_straight: lane marking - arrow (split right or straight)
straight: lane marking - arrow (straight)
crosswalk_zebra: lane marking - crosswalk
give_way_row: lane marking - give way (row)
give_way_single: lane marking - give way (single)
other_marking: lane marking - other
stop_line: lane marking - stop line
symbol:
bicycle: lane marking - symbol (bicycle)
text: lane marking - text
object:
banner: banner
bench: bench
@@ -1331,15 +1347,24 @@ en:
catch_basin: catch basin
cctv_camera: CCTV camera
fire_hydrant: fire hydrant
junction_box: junction box
mailbox: mailbox
manhole: manhole
parking_meter: parking meter
phone_booth: phone booth
sign:
advertisement: advertisement
information: information sign
other: other sign
store: shop sign
traffic_sign:
back: traffic sign - back
direction_front: traffic sign - front
front: traffic sign - front
street_light: street light
support:
pole: pole
traffic_sign_frame: traffic sign frame
utility_pole: utility pole
traffic_cone: traffic cone
traffic_light:
@@ -1350,6 +1375,7 @@ en:
other: traffic light
pedestrians: pedestrian traffic light
trash_can: trash can
water_valve: water valve
mapillary:
title: Mapillary
signs:
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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) {
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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() {
+1 -1
View File
@@ -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() {
+54 -84
View File
@@ -6,13 +6,12 @@ import { services } from '../services';
export function svgMapillaryImages(projection, context, dispatch) {
var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
var minZoom = 12;
var minMarkerZoom = 16;
var minViewfieldZoom = 18;
var layer = d3_select(null);
var _mapillary;
var viewerCompassAngle;
const throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
const minZoom = 12;
const minMarkerZoom = 16;
const minViewfieldZoom = 18;
let layer = d3_select(null);
let _mapillary;
function init() {
@@ -35,7 +34,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
function showLayer() {
var service = getService();
const service = getService();
if (!service) return;
editOn();
@@ -71,39 +70,38 @@ export function svgMapillaryImages(projection, context, dispatch) {
}
function click(d3_event, d) {
var service = getService();
function click(d3_event, image) {
const service = getService();
if (!service) return;
service
.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(d) {
var service = getService();
if (service) service.setStyles(context, d);
function mouseover(d3_event, image) {
const service = getService();
if (service) service.setStyles(context, image);
}
function mouseout() {
var service = getService();
const service = getService();
if (service) service.setStyles(context, null);
}
function transform(d) {
var t = svgPointTransform(projection)(d);
if (d.pano && viewerCompassAngle !== null && isFinite(viewerCompassAngle)) {
t += ' rotate(' + Math.floor(viewerCompassAngle) + ',0,0)';
} else if (d.ca) {
let t = svgPointTransform(projection)(d);
if (d.ca) {
t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
}
return t;
@@ -111,82 +109,54 @@ export function svgMapillaryImages(projection, context, dispatch) {
function filterImages(images) {
var showsPano = context.photos().showsPanoramic();
var showsFlat = context.photos().showsFlat();
var fromDate = context.photos().fromDate();
var toDate = context.photos().toDate();
var usernames = context.photos().usernames();
const showsPano = context.photos().showsPanoramic();
const showsFlat = context.photos().showsFlat();
const fromDate = context.photos().fromDate();
const toDate = context.photos().toDate();
if (!showsPano || !showsFlat) {
images = images.filter(function(image) {
if (image.pano) return showsPano;
if (image.is_pano) return showsPano;
return showsFlat;
});
}
if (fromDate) {
var fromTimestamp = new Date(fromDate).getTime();
images = images.filter(function(image) {
return new Date(image.captured_at).getTime() >= fromTimestamp;
return new Date(image.captured_at).getTime() >= new Date(fromDate).getTime();
});
}
if (toDate) {
var toTimestamp = new Date(toDate).getTime();
images = images.filter(function(image) {
return new Date(image.captured_at).getTime() <= toTimestamp;
});
}
if (usernames) {
images = images.filter(function(image) {
return usernames.indexOf(image.captured_by) !== -1;
return new Date(image.captured_at).getTime() <= new Date(toDate).getTime();
});
}
return images;
}
function filterSequences(sequences, service) {
var showsPano = context.photos().showsPanoramic();
var showsFlat = context.photos().showsFlat();
var fromDate = context.photos().fromDate();
var toDate = context.photos().toDate();
var usernames = context.photos().usernames();
function filterSequences(sequences) {
const showsPano = context.photos().showsPanoramic();
const showsFlat = context.photos().showsFlat();
const fromDate = context.photos().fromDate();
const toDate = context.photos().toDate();
if (!showsPano || !showsFlat) {
sequences = sequences.filter(function(sequence) {
if (sequence.properties.hasOwnProperty('pano')) {
if (sequence.properties.pano) return showsPano;
if (sequence.properties.hasOwnProperty('is_pano')) {
if (sequence.properties.is_pano) return showsPano;
return showsFlat;
} else {
// if the sequence doesn't specify pano or not, search its images
var cProps = sequence.properties.coordinateProperties;
if (cProps && cProps.image_keys && cProps.image_keys.length > 0) {
for (var index in cProps.image_keys) {
var imageKey = cProps.image_keys[index];
var image = service.cachedImage(imageKey);
if (image && image.hasOwnProperty('pano')) {
if (image.pano) return showsPano;
return showsFlat;
}
}
}
}
return false;
});
}
if (fromDate) {
var fromTimestamp = new Date(fromDate).getTime();
sequences = sequences.filter(function(sequence) {
return new Date(sequence.properties.captured_at).getTime() >= fromTimestamp;
return new Date(sequence.properties.captured_at).getTime() >= new Date(fromDate).getTime().toString();
});
}
if (toDate) {
var toTimestamp = new Date(toDate).getTime();
sequences = sequences.filter(function(sequence) {
return new Date(sequence.properties.captured_at).getTime() <= toTimestamp;
});
}
if (usernames) {
sequences = sequences.filter(function(sequence) {
return usernames.indexOf(sequence.properties.username) !== -1;
return new Date(sequence.properties.captured_at).getTime() <= new Date(toDate).getTime().toString();
});
}
@@ -195,20 +165,21 @@ export function svgMapillaryImages(projection, context, dispatch) {
function update() {
var z = ~~context.map().zoom();
var showMarkers = (z >= minMarkerZoom);
var showViewfields = (z >= minViewfieldZoom);
const z = ~~context.map().zoom();
const showMarkers = (z >= minMarkerZoom);
const showViewfields = (z >= minViewfieldZoom);
var service = getService();
var sequences = (service ? service.sequences(projection) : []);
var images = (service && showMarkers ? service.images(projection) : []);
const service = getService();
let sequences = (service ? service.sequences(projection) : []);
let images = (service && showMarkers ? service.images(projection) : []);
images = filterImages(images);
sequences = filterSequences(sequences, service);
service.filterViewer(context);
var traces = layer.selectAll('.sequences').selectAll('.sequence')
.data(sequences, function(d) { return d.properties.key; });
let traces = layer.selectAll('.sequences').selectAll('.sequence')
.data(sequences, function(d) { return d.properties.id; });
// exit
traces.exit()
@@ -222,15 +193,15 @@ export function svgMapillaryImages(projection, context, dispatch) {
.attr('d', svgPath(projection).geojson);
var groups = layer.selectAll('.markers').selectAll('.viewfield-group')
.data(images, function(d) { return d.key; });
const groups = layer.selectAll('.markers').selectAll('.viewfield-group')
.data(images, function(d) { return d.id; });
// exit
groups.exit()
.remove();
// enter
var groupsEnter = groups.enter()
const groupsEnter = groups.enter()
.append('g')
.attr('class', 'viewfield-group')
.on('mouseenter', mouseover)
@@ -242,7 +213,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
.attr('class', 'viewfield-scale');
// update
var markers = groups
const markers = groups
.merge(groupsEnter)
.sort(function(a, b) {
return b.loc[1] - a.loc[1]; // sort Y
@@ -259,7 +230,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
.attr('dy', '0')
.attr('r', '6');
var viewfields = markers.selectAll('.viewfield')
const viewfields = markers.selectAll('.viewfield')
.data(showViewfields ? [0] : []);
viewfields.exit()
@@ -268,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() {
var 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';
@@ -284,8 +254,8 @@ export function svgMapillaryImages(projection, context, dispatch) {
function drawImages(selection) {
var enabled = svgMapillaryImages.enabled;
var service = getService();
const enabled = svgMapillaryImages.enabled;
const service = getService();
layer = selection.selectAll('.layer-mapillary')
.data(service ? [0] : []);
@@ -293,7 +263,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
layer.exit()
.remove();
var layerEnter = layer.enter()
const layerEnter = layer.enter()
.append('g')
.attr('class', 'layer-mapillary')
.style('display', enabled ? 'block' : 'none');
+38 -76
View File
@@ -5,10 +5,10 @@ import { services } from '../services';
import { t } from '../core/localizer';
export function svgMapillaryMapFeatures(projection, context, dispatch) {
var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
var minZoom = 12;
var layer = d3_select(null);
var _mapillary;
const throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
const minZoom = 12;
let layer = d3_select(null);
let _mapillary;
function init() {
@@ -30,7 +30,7 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) {
function showLayer() {
var service = getService();
const service = getService();
if (!service) return;
service.loadObjectResources(context);
@@ -56,88 +56,69 @@ export function svgMapillaryMapFeatures(projection, context, dispatch) {
function click(d3_event, d) {
var service = getService();
const service = getService();
if (!service) return;
context.map().centerEase(d.loc);
var selectedImageKey = service.getSelectedImageKey();
var imageKey;
var highlightedDetection;
// Pick one of the images the map feature was detected in,
// preference given to an image already selected.
d.detections.forEach(function(detection) {
if (!imageKey || selectedImageKey === detection.image_key) {
imageKey = detection.image_key;
highlightedDetection = detection;
const selectedImageId = service.getActiveImage() && service.getActiveImage().id;
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);
});
}
}
function filterData(detectedFeatures) {
var service = getService();
var fromDate = context.photos().fromDate();
var toDate = context.photos().toDate();
var usernames = context.photos().usernames();
const fromDate = context.photos().fromDate();
const toDate = context.photos().toDate();
if (fromDate) {
var fromTimestamp = new Date(fromDate).getTime();
detectedFeatures = detectedFeatures.filter(function(feature) {
return new Date(feature.last_seen_at).getTime() >= fromTimestamp;
return new Date(feature.last_seen_at).getTime() >= new Date(fromDate).getTime();
});
}
if (toDate) {
var toTimestamp = new Date(toDate).getTime();
detectedFeatures = detectedFeatures.filter(function(feature) {
return new Date(feature.first_seen_at).getTime() <= toTimestamp;
});
}
if (usernames && service) {
detectedFeatures = detectedFeatures.filter(function(feature) {
return feature.detections.some(function(detection) {
var imageKey = detection.image_key;
var image = service.cachedImage(imageKey);
return image && usernames.indexOf(image.captured_by) !== -1;
});
return new Date(feature.first_seen_at).getTime() <= new Date(toDate).getTime();
});
}
return detectedFeatures;
}
function update() {
var service = getService();
var data = (service ? service.mapFeatures(projection) : []);
const service = getService();
let data = (service ? service.mapFeatures(projection) : []);
data = filterData(data);
var selectedImageKey = service && service.getSelectedImageKey();
var transform = svgPointTransform(projection);
const transform = svgPointTransform(projection);
var mapFeatures = layer.selectAll('.icon-map-feature')
.data(data, function(d) { return d.key; });
const mapFeatures = layer.selectAll('.icon-map-feature')
.data(data, function(d) { return d.id; });
// exit
mapFeatures.exit()
.remove();
// enter
var enter = mapFeatures.enter()
const enter = mapFeatures.enter()
.append('g')
.attr('class', 'icon-map-feature icon-detected')
.on('click', click);
@@ -173,32 +154,13 @@ 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) {
var aSelected = a.detections.some(function(detection) {
return detection.image_key === selectedImageKey;
});
var 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);
}
function drawMapFeatures(selection) {
var enabled = svgMapillaryMapFeatures.enabled;
var service = getService();
const enabled = svgMapillaryMapFeatures.enabled;
const service = getService();
layer = selection.selectAll('.layer-mapillary-map-features')
.data(service ? [0] : []);
+23 -33
View File
@@ -6,12 +6,12 @@ import { services } from '../services';
export function svgMapillaryPosition(projection, context) {
var throttledRedraw = _throttle(function () { update(); }, 1000);
var minZoom = 12;
var minViewfieldZoom = 18;
var layer = d3_select(null);
var _mapillary;
var viewerCompassAngle;
const throttledRedraw = _throttle(function () { update(); }, 1000);
const minZoom = 12;
const minViewfieldZoom = 18;
let layer = d3_select(null);
let _mapillary;
let viewerCompassAngle;
function init() {
@@ -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);
});
@@ -54,8 +54,8 @@ export function svgMapillaryPosition(projection, context) {
function transform(d) {
var t = svgPointTransform(projection)(d);
if (d.pano && viewerCompassAngle !== null && isFinite(viewerCompassAngle)) {
let t = svgPointTransform(projection)(d);
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)';
@@ -65,21 +65,21 @@ export function svgMapillaryPosition(projection, context) {
function update() {
var z = ~~context.map().zoom();
var showViewfields = (z >= minViewfieldZoom);
const z = ~~context.map().zoom();
const showViewfields = (z >= minViewfieldZoom);
var service = getService();
var node = service && service.getActiveImage();
const service = getService();
const image = service && service.getActiveImage();
var groups = layer.selectAll('.markers').selectAll('.viewfield-group')
.data(node ? [node] : [], function(d) { return d.key; });
const groups = layer.selectAll('.markers').selectAll('.viewfield-group')
.data(image ? [image] : [], function(d) { return d.id; });
// exit
groups.exit()
.remove();
// enter
var groupsEnter = groups.enter()
const groupsEnter = groups.enter()
.append('g')
.attr('class', 'viewfield-group currentView highlighted');
@@ -89,7 +89,7 @@ export function svgMapillaryPosition(projection, context) {
.attr('class', 'viewfield-scale');
// update
var markers = groups
const markers = groups
.merge(groupsEnter)
.attr('transform', transform)
.select('.viewfield-scale');
@@ -103,7 +103,7 @@ export function svgMapillaryPosition(projection, context) {
.attr('dy', '0')
.attr('r', '6');
var viewfields = markers.selectAll('.viewfield')
const viewfields = markers.selectAll('.viewfield')
.data(showViewfields ? [0] : []);
viewfields.exit()
@@ -112,23 +112,13 @@ 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() {
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';
}
}
.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');
}
function drawImages(selection) {
var service = getService();
const service = getService();
layer = selection.selectAll('.layer-mapillary-position')
.data(service ? [0] : []);
@@ -136,7 +126,7 @@ export function svgMapillaryPosition(projection, context) {
layer.exit()
.remove();
var layerEnter = layer.enter()
const layerEnter = layer.enter()
.append('g')
.attr('class', 'layer-mapillary-position');
+34 -71
View File
@@ -5,10 +5,10 @@ import { services } from '../services';
export function svgMapillarySigns(projection, context, dispatch) {
var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
var minZoom = 12;
var layer = d3_select(null);
var _mapillary;
const throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000);
const minZoom = 12;
let layer = d3_select(null);
let _mapillary;
function init() {
@@ -30,7 +30,7 @@ export function svgMapillarySigns(projection, context, dispatch) {
function showLayer() {
var service = getService();
const service = getService();
if (!service) return;
service.loadSignResources(context);
@@ -56,46 +56,37 @@ export function svgMapillarySigns(projection, context, dispatch) {
function click(d3_event, d) {
var service = getService();
const service = getService();
if (!service) return;
context.map().centerEase(d.loc);
var selectedImageKey = service.getSelectedImageKey();
var imageKey;
var highlightedDetection;
// Pick one of the images the sign was detected in,
// preference given to an image already selected.
d.detections.forEach(function(detection) {
if (!imageKey || selectedImageKey === detection.image_key) {
imageKey = detection.image_key;
highlightedDetection = detection;
const selectedImageId = service.getActiveImage() && service.getActiveImage().id;
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);
});
}
}
function filterData(detectedFeatures) {
var service = getService();
var fromDate = context.photos().fromDate();
var toDate = context.photos().toDate();
var usernames = context.photos().usernames();
if (fromDate) {
var fromTimestamp = new Date(fromDate).getTime();
@@ -109,36 +100,27 @@ export function svgMapillarySigns(projection, context, dispatch) {
return new Date(feature.first_seen_at).getTime() <= toTimestamp;
});
}
if (usernames && service) {
detectedFeatures = detectedFeatures.filter(function(feature) {
return feature.detections.some(function(detection) {
var imageKey = detection.image_key;
var image = service.cachedImage(imageKey);
return image && usernames.indexOf(image.captured_by) !== -1;
});
});
}
return detectedFeatures;
}
function update() {
var service = getService();
var data = (service ? service.signs(projection) : []);
const service = getService();
let data = (service ? service.signs(projection) : []);
data = filterData(data);
var selectedImageKey = service.getSelectedImageKey();
var transform = svgPointTransform(projection);
const transform = svgPointTransform(projection);
var signs = layer.selectAll('.icon-sign')
.data(data, function(d) { return d.key; });
const signs = layer.selectAll('.icon-sign')
.data(data, function(d) { return d.id; });
// exit
signs.exit()
.remove();
// enter
var enter = signs.enter()
const enter = signs.enter()
.append('g')
.attr('class', 'icon-sign icon-detected')
.on('click', click);
@@ -161,32 +143,13 @@ 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) {
var aSelected = a.detections.some(function(detection) {
return detection.image_key === selectedImageKey;
});
var 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);
}
function drawSigns(selection) {
var enabled = svgMapillarySigns.enabled;
var service = getService();
const enabled = svgMapillarySigns.enabled;
const service = getService();
layer = selection.selectAll('.layer-mapillary-signs')
.data(service ? [0] : []);
+1 -1
View File
@@ -94,7 +94,7 @@
"js-yaml": "^4.0.0",
"json-stringify-pretty-compact": "^3.0.0",
"mapillary_sprite_source": "^1.8.0",
"mapillary-js": "~3.1.0",
"mapillary-js": "4.0.0",
"minimist": "^1.2.3",
"mocha": "^7.0.1",
"mocha-phantomjs-core": "^2.1.0",
+55 -260
View File
@@ -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();
@@ -42,6 +43,7 @@ describe('iD.serviceMapillary', function() {
});
});
describe('#reset', function() {
it('resets cache and image', function() {
mapillary.cache().foo = 'bar';
@@ -49,227 +51,7 @@ describe('iD.serviceMapillary', function() {
mapillary.reset();
expect(mapillary.cache()).to.not.have.property('foo');
expect(mapillary.getSelectedImageKey()).to.be.null;
});
});
describe('#loadImages', function() {
it('fires loadedImages when images are loaded', function(done) {
var spy = sinon.spy();
mapillary.on('loadedImages', spy);
mapillary.loadImages(context.projection);
var features = [{
type: 'Feature',
geometry: { type: 'Point', coordinates: [10,0] },
properties: { ca: 90, key: '0' }
}];
var response = { type: 'FeatureCollection', features: features };
server.respondWith('GET', /images/,
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]);
server.respond();
window.setTimeout(function() {
expect(spy).to.have.been.called;
expect(server.requests().length).to.eql(2);
done();
}, 500);
});
it('does not load images around null island', function(done) {
var spy = sinon.spy();
context.projection.translate([0,0]);
mapillary.on('loadedImages', spy);
mapillary.loadImages(context.projection);
var features = [{
type: 'Feature',
geometry: { type: 'Point', coordinates: [0,0] },
properties: { ca: 90, key: '0' }
}];
var response = { type: 'FeatureCollection', features: features };
server.respondWith('GET', /images/,
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]);
server.respond();
window.setTimeout(function() {
expect(spy).to.have.been.not.called;
expect(server.requests().length).to.eql(0); // no tile requests of any kind
done();
}, 200);
});
it('loads multiple pages of image results', function(done) {
var calls = 0;
mapillary.on('loadedImages', function() {
server.respond(); // respond to new fetches
if (++calls === 2) {
expect(server.requests().length).to.eql(3); // 2 images, 1 sequences
done();
}
});
mapillary.loadImages(context.projection);
var features0 = [];
var features1 = [];
var i, key;
for (i = 0; i < 1000; i++) {
key = String(i);
features0.push({
type: 'Feature',
geometry: { type: 'Point', coordinates: [10,0] },
properties: { ca: 90, key: key }
});
}
for (i = 0; i < 500; i++) {
key = String(1000 + i);
features1.push({
type: 'Feature',
geometry: { type: 'Point', coordinates: [10,0] },
properties: { ca: 90, key: key }
});
}
var response0 = { type: 'FeatureCollection', features: features0 };
var response1 = { type: 'FeatureCollection', features: features1 };
server.respondWith('GET', /\/images\?.*&page=0/,
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response0) ]);
server.respondWith('GET', /\/images\?.*&page=1/,
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response1) ]);
server.respond();
});
});
describe('#loadSigns', function() {
it('fires loadedSigns when signs are loaded', function(done) {
var spy = sinon.spy();
mapillary.on('loadedSigns', spy);
mapillary.loadSigns(context.projection);
var detections = [{ detection_key: '0', image_key: '0' }];
var features = [{
type: 'Feature',
geometry: { type: 'Point', coordinates: [10,0] },
properties: { detections: detections, key: '0', value: 'not-in-set' }
}];
var response = { type: 'FeatureCollection', features: features };
server.respondWith('GET', /map_features/,
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]);
server.respond();
window.setTimeout(function() {
expect(spy).to.have.been.called;
expect(server.requests().length).to.eql(1);
done();
}, 200);
});
it('does not load signs around null island', function(done) {
var spy = sinon.spy();
context.projection.translate([0,0]);
mapillary.on('loadedSigns', spy);
mapillary.loadSigns(context.projection);
var detections = [{ detection_key: '0', image_key: '0' }];
var features = [{
type: 'Feature',
geometry: { type: 'Point', coordinates: [0,0] },
properties: { detections: detections, key: '0', value: 'not-in-set' }
}];
var response = { type: 'FeatureCollection', features: features };
server.respondWith('GET', /map_features/,
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]);
server.respond();
window.setTimeout(function() {
expect(spy).to.have.been.not.called;
expect(server.requests().length).to.eql(0); // no tile requests of any kind
done();
}, 200);
});
it.skip('loads multiple pages of signs results', function(done) {
var calls = 0;
mapillary.on('loadedSigns', function() {
server.respond(); // respond to new fetches
if (++calls === 2) {
expect(server.requests().length).to.eql(4); // 2 images, 1 map_features, 1 image_detections
done();
}
});
mapillary.loadSigns(context.projection);
var features0 = [];
var features1 = [];
var i, key, detections;
for (i = 0; i < 1000; i++) {
key = String(i);
detections = [{ detection_key: key, image_key: key }];
features0.push({
type: 'Feature',
geometry: { type: 'Point', coordinates: [10,0] },
properties: { detections: detections, key: key, value: 'not-in-set' }
});
}
for (i = 0; i < 500; i++) {
key = String(1000 + i);
detections = [{ detection_key: key, image_key: key }];
features1.push({
type: 'Feature',
geometry: { type: 'Point', coordinates: [10,0] },
properties: { detections: detections, key: key, value: 'not-in-set' }
});
}
var response0 = { type: 'FeatureCollection', features: features0 };
var response1 = { type: 'FeatureCollection', features: features1 };
server.respondWith('GET', /\/map_features\?.*&page=0/,
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response0) ]);
server.respondWith('GET', /\/map_features\?.*&page=1/,
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response1) ]);
server.respond();
});
});
describe('#loadMapFeatures', function() {
it('fires loadedMapFeatures when map features are loaded', function(done) {
var spy = sinon.spy();
mapillary.on('loadedMapFeatures', spy);
mapillary.loadMapFeatures(context.projection);
var detections = [{ detection_key: '0', image_key: '0' }];
var features = [{
type: 'Feature',
geometry: { type: 'Point', coordinates: [10,0] },
properties: { detections: detections, key: '0', value: 'not-in-set' }
}];
var response = { type: 'FeatureCollection', features: features };
server.respondWith('GET', /map_features/,
[200, { 'Content-Type': 'application/json' }, JSON.stringify(response) ]);
server.respond();
window.setTimeout(function() {
expect(spy).to.have.been.called;
expect(server.requests().length).to.eql(1);
done();
}, 500);
expect(mapillary.getActiveImage()).to.be.null;
});
});
@@ -310,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'
@@ -343,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);
});
});
@@ -353,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 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90 } },
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], ca: 90 } }
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90, 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);
@@ -376,40 +188,24 @@ describe('iD.serviceMapillary', function() {
}
};
mapillary.cache().sequences.lineString['-'] = gj;
mapillary.cache().sequences.forImageKey['0'] = '-';
mapillary.cache().sequences.forImageKey['1'] = '-';
mapillary.cache().sequences.forImageKey['2'] = '-';
mapillary.cache().sequences.lineString['-'] = [gj];
var res = mapillary.sequences(context.projection);
expect(res).to.deep.eql([gj]);
});
});
describe('#selectImage', function() {
describe('#setActiveImage', function() {
it('gets and sets the selected image', function() {
var d = { key: 'baz', loc: [10,0] };
mapillary.selectImage(context, d.key);
expect(mapillary.getSelectedImageKey()).to.eql(d.key);
var node = { id: 'baz', originalLngLat: {lng: 10, lat: 0}};
mapillary.setActiveImage(node);
expect(mapillary.getActiveImage().id).to.eql(node.id);
});
});
describe('#parsePagination', function() {
it('gets URL for next page of results from API', function() {
var linkHeader = '<https://a.mapillary.com/v3/images?per_page=1000>; rel="first", <https://a.mapillary.com/v3/images?per_page=1000&_start_key_time=1476610926080>; rel="next"';
var pagination = mapillary.parsePagination(linkHeader);
expect(pagination.first).to.eql('https://a.mapillary.com/v3/images?per_page=1000');
expect(pagination.next).to.eql('https://a.mapillary.com/v3/images?per_page=1000&_start_key_time=1476610926080');
});
});
describe('#filterViewer', function() {
it('filters images by username', function() {
context.photos().setUsernameFilter('mapillary');
var filter = mapillary.filterViewer(context);
expect(filter.length).to.be.equal(2);
});
it('filters images by dates', function() {
context.photos().setDateFilter('fromDate', '2020-01-01');
context.photos().setDateFilter('toDate', '2021-01-01');
@@ -417,5 +213,4 @@ describe('iD.serviceMapillary', function() {
expect(filter.length).to.be.equal(3);
});
});
});