Use new Mapillary API calls for map_features, image_detections

(closes #5374)
This commit is contained in:
Bryan Housel
2018-11-07 23:09:11 -05:00
parent 878bfdd144
commit b7df3eda38
2 changed files with 66 additions and 73 deletions
+62 -69
View File
@@ -2,7 +2,6 @@
import _find from 'lodash-es/find';
import _flatten from 'lodash-es/flatten';
import _forEach from 'lodash-es/forEach';
import _isEmpty from 'lodash-es/isEmpty';
import _map from 'lodash-es/map';
import _some from 'lodash-es/some';
import _union from 'lodash-es/union';
@@ -111,6 +110,9 @@ function loadNextTilePage(which, currZoom, url, tile) {
var loc = feature.geometry.coordinates;
var d;
// An image (shown as a green dot on the map) is a single street photo with extra
// information such as location, camera angle (CA), camera model, and so on.
// Each image feature is a GeoJSON Point
if (which === 'images') {
d = {
loc: loc,
@@ -120,17 +122,44 @@ function loadNextTilePage(which, currZoom, url, tile) {
captured_by: feature.properties.username,
pano: feature.properties.pano
};
cache.forImageKey[d.key] = d; // cache imageKey -> image
// Mapillary organizes images as sequences. A sequence of images are continuously captured
// by a user at a give time. Sequences are shown on the map as green lines.
// Each sequence feature is a GeoJSON LineString
} else if (which === 'sequences') {
var sequenceKey = feature.properties.key;
cache.lineString[sequenceKey] = feature; // cache sequenceKey -> lineString
feature.properties.coordinateProperties.image_keys.forEach(function(imageKey) {
cache.forImageKey[imageKey] = sequenceKey; // cache imageKey -> sequenceKey
});
return false; // because no `d` data worth loading into an rbush
return false; // because no `d` data worth loading into an rbush
} else if (which === 'objects') {
// 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)
} else if (which === 'map_features') {
d = {
loc: loc,
key: feature.properties.key,
@@ -138,18 +167,6 @@ function loadNextTilePage(which, currZoom, url, tile) {
package: feature.properties.package,
detections: feature.properties.detections
};
// cache imageKey -> detectionKey
feature.properties.detections.forEach(function(detection) {
var imageKey = detection.image_key;
var detectionKey = detection.detection_key;
if (!_mlyCache.detections[imageKey]) {
_mlyCache.detections[imageKey] = {};
}
if (!_mlyCache.detections[imageKey][detectionKey]) {
_mlyCache.detections[imageKey][detectionKey] = {};
}
});
}
return {
@@ -158,11 +175,13 @@ function loadNextTilePage(which, currZoom, url, tile) {
}).filter(Boolean);
cache.rtree.load(features);
if (cache.rtree && features) {
cache.rtree.load(features);
}
if (which === 'images' || which === 'sequences') {
dispatch.call('loadedImages');
} else if (which === 'objects') {
} else if (which === 'map_features') {
dispatch.call('loadedSigns');
}
@@ -266,8 +285,11 @@ export default {
if (cache.images && cache.images.inflight) {
_forEach(cache.images.inflight, abortRequest);
}
if (cache.objects && cache.objects.inflight) {
_forEach(cache.objects.inflight, abortRequest);
if (cache.image_detections && cache.image_detections.inflight) {
_forEach(cache.image_detections.inflight, abortRequest);
}
if (cache.map_features && cache.map_features.inflight) {
_forEach(cache.map_features.inflight, abortRequest);
}
if (cache.sequences && cache.sequences.inflight) {
_forEach(cache.sequences.inflight, abortRequest);
@@ -276,9 +298,9 @@ export default {
_mlyCache = {
images: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush(), forImageKey: {} },
objects: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush() },
sequences: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush(), forImageKey: {}, lineString: {} },
detections: {}
image_detections: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, forImageKey: {} },
map_features: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush() },
sequences: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush(), forImageKey: {}, lineString: {} }
};
_mlySelectedImage = null;
@@ -294,7 +316,7 @@ export default {
signs: function(projection) {
var psize = 32, limit = 3;
return searchLimited(psize, limit, projection, _mlyCache.objects.rtree);
return searchLimited(psize, limit, projection, _mlyCache.map_features.rtree);
},
@@ -335,7 +357,8 @@ export default {
loadSigns: function(context, projection) {
// if we are looking at signs, we'll actually need to fetch images too
loadTiles('images', apibase + 'images?', projection);
loadTiles('objects', apibase + 'map_features?layers=trafficsigns&', projection);
loadTiles('map_features', apibase + 'map_features?layers=trafficsigns&min_nbr_image_detections=1&', projection);
loadTiles('image_detections', apibase + 'image_detections?layers=trafficsigns&', projection);
},
@@ -537,6 +560,7 @@ export default {
this.setStyles(null, true);
// if signs signs are shown, highlight the ones that appear in this image
d3_selectAll('.layer-mapillary-signs .icon-sign')
.classed('selected', function(d) {
return _some(d.detections, function(detection) {
@@ -620,63 +644,32 @@ export default {
if (!_mlyViewer || _mlyFallback) return;
var imageKey = d && d.key;
var detections = (imageKey && _mlyCache.detections[imageKey]) || [];
if (!imageKey) return;
_forEach(detections, function(data, k) {
if (_isEmpty(data)) {
loadDetection(k);
} else {
var tag = makeTag(data);
if (tag) {
var tagComponent = _mlyViewer.getComponent('tag');
tagComponent.add([tag]);
}
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]);
}
});
function loadDetection(detectionKey) {
var url = apibase + 'image_detections/' +
detectionKey + '?' + utilQsString({ client_id: clientId });
d3_request(url)
.mimeType('application/json')
.response(function(xhr) {
return JSON.parse(xhr.responseText);
})
.get(function(err, data) {
if (!data || !data.properties) return;
var imageKey = data.properties.image_key;
_mlyCache.detections[imageKey][detectionKey] = data;
var selectedKey = _mlySelectedImage && _mlySelectedImage.key;
if (imageKey === selectedKey) {
var tag = makeTag(data);
if (tag) {
var tagComponent = _mlyViewer.getComponent('tag');
tagComponent.add([tag]);
}
}
});
}
function makeTag(data) {
var valueParts = data.properties.value.split('--');
var valueParts = data.value.split('--');
if (valueParts.length !== 3) return;
var text = valueParts[1].replace(/-/g, ' ');
var tag;
// Currently only two shapes <Polygon|Point>
if (data.properties.shape.type === 'Polygon') {
if (data.shape.type === 'Polygon') {
var polygonGeometry = new Mapillary
.TagComponent
.PolygonGeometry(data.properties.shape.coordinates[0]);
.PolygonGeometry(data.shape.coordinates[0]);
tag = new Mapillary.TagComponent.OutlineTag(
data.properties.key,
data.key,
polygonGeometry,
{
text: text,
@@ -688,13 +681,13 @@ export default {
}
);
} else if (data.properties.shape.type === 'Point') {
} else if (data.shape.type === 'Point') {
var pointGeometry = new Mapillary
.TagComponent
.PointGeometry(data.properties.shape.coordinates[0]);
.PointGeometry(data.shape.coordinates[0]);
tag = new Mapillary.TagComponent.SpotTag(
data.properties.key,
data.key,
pointGeometry,
{
text: text,
+4 -4
View File
@@ -32,9 +32,9 @@ describe('iD.serviceMapillary', function() {
it('Initializes cache one time', function() {
var cache = mapillary.cache();
expect(cache).to.have.property('images');
expect(cache).to.have.property('objects');
expect(cache).to.have.property('image_detections');
expect(cache).to.have.property('map_features');
expect(cache).to.have.property('sequences');
expect(cache).to.have.property('detections');
mapillary.init();
var cache2 = mapillary.cache();
@@ -275,7 +275,7 @@ describe('iD.serviceMapillary', function() {
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], detections: detections } }
];
mapillary.cache().objects.rtree.load(features);
mapillary.cache().map_features.rtree.load(features);
var res = mapillary.signs(context.projection);
expect(res).to.deep.eql([
@@ -297,7 +297,7 @@ describe('iD.serviceMapillary', function() {
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0], detections: detections } }
];
mapillary.cache().objects.rtree.load(features);
mapillary.cache().map_features.rtree.load(features);
var res = mapillary.signs(context.projection);
expect(res).to.have.length.of.at.most(3);
});