mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-14 09:42:56 +00:00
(closes #4526) What we were doing before was d3.selecting the attribution line and inserting the capture date into it. This was confusing the viewer (which updates asynchronously) so that the sitelink would never update. To make things easier, we'll just hide the viewer's built in attribution and construct the attribution line the way we want it.
797 lines
26 KiB
JavaScript
797 lines
26 KiB
JavaScript
/* global Mapillary:false */
|
|
import _filter from 'lodash-es/filter';
|
|
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';
|
|
|
|
import { range as d3_range } from 'd3-array';
|
|
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
|
|
|
import {
|
|
request as d3_request,
|
|
json as d3_json
|
|
} from 'd3-request';
|
|
|
|
import {
|
|
select as d3_select,
|
|
selectAll as d3_selectAll
|
|
} from 'd3-selection';
|
|
|
|
import rbush from 'rbush';
|
|
|
|
import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile';
|
|
import { geoExtent } from '../geo';
|
|
import { utilDetect } from '../util/detect';
|
|
import { utilQsString, utilRebind } from '../util';
|
|
|
|
|
|
var apibase = 'https://a.mapillary.com/v3/',
|
|
viewercss = 'mapillary-js/mapillary.min.css',
|
|
viewerjs = 'mapillary-js/mapillary.min.js',
|
|
clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi',
|
|
maxResults = 1000,
|
|
tileZoom = 14,
|
|
dispatch = d3_dispatch('loadedImages', 'loadedSigns'),
|
|
_mlyCache,
|
|
_mlyClicks,
|
|
_mlySelectedImage,
|
|
_mlySignDefs,
|
|
_mlySignSprite,
|
|
_mlyViewer;
|
|
|
|
|
|
function abortRequest(i) {
|
|
i.abort();
|
|
}
|
|
|
|
|
|
function nearNullIsland(x, y, z) {
|
|
if (z >= 7) {
|
|
var center = Math.pow(2, z - 1),
|
|
width = Math.pow(2, z - 6),
|
|
min = center - (width / 2),
|
|
max = center + (width / 2) - 1;
|
|
return x >= min && x <= max && y >= min && y <= max;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
function maxPageAtZoom(z) {
|
|
if (z < 15) return 2;
|
|
if (z === 15) return 5;
|
|
if (z === 16) return 10;
|
|
if (z === 17) return 20;
|
|
if (z === 18) return 40;
|
|
if (z > 18) return 80;
|
|
}
|
|
|
|
|
|
function localeTimestamp(s) {
|
|
if (!s) return null;
|
|
var d = new Date(s);
|
|
if (isNaN(d.getTime())) return null;
|
|
return d.toLocaleString(undefined, { timeZone: 'UTC' });
|
|
}
|
|
|
|
|
|
function getTiles(projection) {
|
|
var s = projection.scale() * 2 * Math.PI,
|
|
z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
|
|
ts = 256 * Math.pow(2, z - tileZoom),
|
|
origin = [
|
|
s / 2 - projection.translate()[0],
|
|
s / 2 - projection.translate()[1]];
|
|
|
|
return d3_geoTile()
|
|
.scaleExtent([tileZoom, tileZoom])
|
|
.scale(s)
|
|
.size(projection.clipExtent()[1])
|
|
.translate(projection.translate())()
|
|
.map(function(tile) {
|
|
var x = tile[0] * ts - origin[0],
|
|
y = tile[1] * ts - origin[1];
|
|
|
|
return {
|
|
id: tile.toString(),
|
|
xyz: tile,
|
|
extent: geoExtent(
|
|
projection.invert([x, y + ts]),
|
|
projection.invert([x + ts, y])
|
|
)
|
|
};
|
|
});
|
|
}
|
|
|
|
|
|
function loadTiles(which, url, projection) {
|
|
var s = projection.scale() * 2 * Math.PI,
|
|
currZoom = Math.floor(Math.max(Math.log(s) / Math.log(2) - 8, 0));
|
|
|
|
var tiles = getTiles(projection).filter(function(t) {
|
|
return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]);
|
|
});
|
|
|
|
_filter(which.inflight, function(v, k) {
|
|
var wanted = _find(tiles, function(tile) { return k === (tile.id + ',0'); });
|
|
if (!wanted) delete which.inflight[k];
|
|
return !wanted;
|
|
}).map(abortRequest);
|
|
|
|
tiles.forEach(function(tile) {
|
|
loadNextTilePage(which, currZoom, url, tile);
|
|
});
|
|
}
|
|
|
|
|
|
function loadNextTilePage(which, currZoom, url, tile) {
|
|
var cache = _mlyCache[which],
|
|
rect = tile.extent.rectangle(),
|
|
maxPages = maxPageAtZoom(currZoom),
|
|
nextPage = cache.nextPage[tile.id] || 0,
|
|
nextURL = cache.nextURL[tile.id] || url +
|
|
utilQsString({
|
|
per_page: maxResults,
|
|
page: nextPage,
|
|
client_id: clientId,
|
|
bbox: [rect[0], rect[1], rect[2], rect[3]].join(','),
|
|
});
|
|
|
|
if (nextPage > maxPages) return;
|
|
|
|
var id = tile.id + ',' + String(nextPage);
|
|
if (cache.loaded[id] || cache.inflight[id]) return;
|
|
cache.inflight[id] = d3_request(nextURL)
|
|
.mimeType('application/json')
|
|
.response(function(xhr) {
|
|
var linkHeader = xhr.getResponseHeader('Link');
|
|
if (linkHeader) {
|
|
var pagination = parsePagination(xhr.getResponseHeader('Link'));
|
|
if (pagination.next) {
|
|
cache.nextURL[tile.id] = pagination.next;
|
|
}
|
|
}
|
|
return JSON.parse(xhr.responseText);
|
|
})
|
|
.get(function(err, data) {
|
|
cache.loaded[id] = true;
|
|
delete cache.inflight[id];
|
|
if (err || !data.features || !data.features.length) return;
|
|
|
|
var features = data.features.map(function(feature) {
|
|
var loc = feature.geometry.coordinates,
|
|
d;
|
|
|
|
if (which === 'images') {
|
|
d = {
|
|
loc: loc,
|
|
key: feature.properties.key,
|
|
ca: feature.properties.ca,
|
|
captured_at: feature.properties.captured_at,
|
|
captured_by: feature.properties.username,
|
|
pano: feature.properties.pano
|
|
};
|
|
cache.forImageKey[d.key] = d; // cache imageKey -> image
|
|
|
|
} 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
|
|
|
|
} else if (which === 'objects') {
|
|
d = {
|
|
loc: loc,
|
|
key: feature.properties.key,
|
|
value: feature.properties.value,
|
|
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 {
|
|
minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d
|
|
};
|
|
|
|
}).filter(Boolean);
|
|
|
|
cache.rtree.load(features);
|
|
|
|
if (which === 'images' || which === 'sequences') {
|
|
dispatch.call('loadedImages');
|
|
} else if (which === 'objects') {
|
|
dispatch.call('loadedSigns');
|
|
}
|
|
|
|
if (data.features.length === maxResults) { // more pages to load
|
|
cache.nextPage[tile.id] = nextPage + 1;
|
|
loadNextTilePage(which, currZoom, url, tile);
|
|
} else {
|
|
cache.nextPage[tile.id] = Infinity; // no more pages to load
|
|
}
|
|
});
|
|
}
|
|
|
|
// extract links to pages of API results
|
|
function parsePagination(links) {
|
|
return links.split(',').map(function(rel) {
|
|
var elements = rel.split(';');
|
|
if (elements.length === 2) {
|
|
return [
|
|
/<(.+)>/.exec(elements[0])[1],
|
|
/rel="(.+)"/.exec(elements[1])[1]
|
|
];
|
|
} else {
|
|
return ['',''];
|
|
}
|
|
}).reduce(function(pagination, val) {
|
|
pagination[val[1]] = val[0];
|
|
return pagination;
|
|
}, {});
|
|
}
|
|
|
|
|
|
// partition viewport into `psize` x `psize` regions
|
|
function partitionViewport(psize, projection) {
|
|
var dimensions = projection.clipExtent()[1];
|
|
psize = psize || 16;
|
|
var cols = d3_range(0, dimensions[0], psize),
|
|
rows = d3_range(0, dimensions[1], psize),
|
|
partitions = [];
|
|
|
|
rows.forEach(function(y) {
|
|
cols.forEach(function(x) {
|
|
var min = [x, y + psize],
|
|
max = [x + psize, y];
|
|
partitions.push(
|
|
geoExtent(projection.invert(min), projection.invert(max)));
|
|
});
|
|
});
|
|
|
|
return partitions;
|
|
}
|
|
|
|
|
|
// no more than `limit` results per partition.
|
|
function searchLimited(psize, limit, projection, rtree) {
|
|
limit = limit || 3;
|
|
|
|
var partitions = partitionViewport(psize, projection);
|
|
var results;
|
|
|
|
// console.time('previous');
|
|
results = _flatten(_map(partitions, function(extent) {
|
|
return rtree.search(extent.bbox())
|
|
.slice(0, limit)
|
|
.map(function(d) { return d.data; });
|
|
}));
|
|
// console.timeEnd('previous');
|
|
|
|
// console.time('new');
|
|
// results = partitions.reduce(function(result, extent) {
|
|
// var found = rtree.search(extent.bbox())
|
|
// .map(function(d) { return d.data; })
|
|
// .sort(function(a, b) {
|
|
// return a.loc[1] - b.loc[1];
|
|
// // return a.key.localeCompare(b.key);
|
|
// })
|
|
// .slice(0, limit);
|
|
|
|
// return (found.length ? result.concat(found) : result);
|
|
// }, []);
|
|
// console.timeEnd('new');
|
|
|
|
return results;
|
|
}
|
|
|
|
|
|
|
|
export default {
|
|
|
|
init: function() {
|
|
if (!_mlyCache) {
|
|
this.reset();
|
|
}
|
|
|
|
this.event = utilRebind(this, dispatch, 'on');
|
|
},
|
|
|
|
reset: function() {
|
|
var cache = _mlyCache;
|
|
|
|
if (cache) {
|
|
if (cache.images && cache.images.inflight) {
|
|
_forEach(cache.images.inflight, abortRequest);
|
|
}
|
|
if (cache.objects && cache.objects.inflight) {
|
|
_forEach(cache.objects.inflight, abortRequest);
|
|
}
|
|
if (cache.sequences && cache.sequences.inflight) {
|
|
_forEach(cache.sequences.inflight, abortRequest);
|
|
}
|
|
}
|
|
|
|
_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: {}
|
|
};
|
|
|
|
_mlySelectedImage = null;
|
|
_mlyClicks = [];
|
|
},
|
|
|
|
|
|
images: function(projection) {
|
|
var psize = 16, limit = 3;
|
|
return searchLimited(psize, limit, projection, _mlyCache.images.rtree);
|
|
},
|
|
|
|
|
|
signs: function(projection) {
|
|
var psize = 32, limit = 3;
|
|
return searchLimited(psize, limit, projection, _mlyCache.objects.rtree);
|
|
},
|
|
|
|
|
|
sequences: function(projection) {
|
|
var viewport = projection.clipExtent();
|
|
var min = [viewport[0][0], viewport[1][1]];
|
|
var max = [viewport[1][0], viewport[0][1]];
|
|
var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
|
|
var sequenceKeys = {};
|
|
|
|
// all sequences for images in viewport
|
|
_mlyCache.images.rtree.search(bbox)
|
|
.forEach(function(d) {
|
|
var sequenceKey = _mlyCache.sequences.forImageKey[d.data.key];
|
|
if (sequenceKey) {
|
|
sequenceKeys[sequenceKey] = true;
|
|
}
|
|
});
|
|
|
|
// Return lineStrings for the sequences
|
|
return Object.keys(sequenceKeys).map(function(sequenceKey) {
|
|
return _mlyCache.sequences.lineString[sequenceKey];
|
|
});
|
|
},
|
|
|
|
|
|
signsSupported: function() {
|
|
var detected = utilDetect();
|
|
if (detected.ie) return false;
|
|
if ((detected.browser.toLowerCase() === 'safari') && (parseFloat(detected.version) < 10)) return false;
|
|
return true;
|
|
},
|
|
|
|
|
|
signHTML: function(d) {
|
|
if (!_mlySignDefs || !_mlySignSprite) return;
|
|
var position = _mlySignDefs[d.value];
|
|
if (!position) return '<div></div>';
|
|
var iconStyle = [
|
|
'background-image:url(' + _mlySignSprite + ')',
|
|
'background-repeat:no-repeat',
|
|
'height:' + position.height + 'px',
|
|
'width:' + position.width + 'px',
|
|
'background-position-x:-' + position.x + 'px',
|
|
'background-position-y:-' + position.y + 'px',
|
|
];
|
|
|
|
return '<div style="' + iconStyle.join(';') +'"></div>';
|
|
},
|
|
|
|
|
|
loadImages: function(projection) {
|
|
loadTiles('images', apibase + 'images?', projection);
|
|
loadTiles('sequences', apibase + 'sequences?', projection);
|
|
},
|
|
|
|
|
|
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 + 'objects?', projection);
|
|
|
|
// load traffic sign defs
|
|
if (!_mlySignDefs) {
|
|
_mlySignSprite = context.asset('img/traffic-signs/traffic-signs.png');
|
|
_mlySignDefs = {};
|
|
d3_json(context.asset('img/traffic-signs/traffic-signs.json'), function(err, data) {
|
|
if (err) return;
|
|
_mlySignDefs = data;
|
|
});
|
|
}
|
|
},
|
|
|
|
|
|
loadViewer: function(context) {
|
|
// add mly-wrapper
|
|
var wrap = d3_select('#photoviewer').selectAll('.mly-wrapper')
|
|
.data([0]);
|
|
|
|
var wrapEnter = wrap.enter()
|
|
.append('div')
|
|
.attr('id', 'mly')
|
|
.attr('class', 'photo-wrapper mly-wrapper')
|
|
.classed('hide', true);
|
|
|
|
wrapEnter
|
|
.append('div')
|
|
.attr('class', 'photo-attribution fillD');
|
|
|
|
|
|
// load mapillary-viewercss
|
|
d3_select('head').selectAll('#mapillary-viewercss')
|
|
.data([0])
|
|
.enter()
|
|
.append('link')
|
|
.attr('id', 'mapillary-viewercss')
|
|
.attr('rel', 'stylesheet')
|
|
.attr('href', context.asset(viewercss));
|
|
|
|
// load mapillary-viewerjs
|
|
d3_select('head').selectAll('#mapillary-viewerjs')
|
|
.data([0])
|
|
.enter()
|
|
.append('script')
|
|
.attr('id', 'mapillary-viewerjs')
|
|
.attr('src', context.asset(viewerjs));
|
|
},
|
|
|
|
|
|
showViewer: function() {
|
|
var wrap = d3_select('#photoviewer')
|
|
.classed('hide', false);
|
|
|
|
var isHidden = wrap.selectAll('.photo-wrapper.mly-wrapper.hide').size();
|
|
|
|
if (isHidden) {
|
|
wrap
|
|
.selectAll('.photo-wrapper:not(.mly-wrapper)')
|
|
.classed('hide', true);
|
|
|
|
wrap
|
|
.selectAll('.photo-wrapper.mly-wrapper')
|
|
.classed('hide', false);
|
|
|
|
_mlyViewer.resize();
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
|
|
hideViewer: function() {
|
|
_mlySelectedImage = null;
|
|
|
|
var viewer = d3_select('#photoviewer');
|
|
if (!viewer.empty()) viewer.datum(null);
|
|
|
|
viewer
|
|
.classed('hide', true)
|
|
.selectAll('.photo-wrapper')
|
|
.classed('hide', true);
|
|
|
|
d3_selectAll('.viewfield-group, .sequence, .icon-sign')
|
|
.classed('selected', false);
|
|
|
|
return this.setStyles(null, true);
|
|
},
|
|
|
|
|
|
parsePagination: parsePagination,
|
|
|
|
|
|
updateViewer: function(imageKey, context) {
|
|
if (!imageKey) return this;
|
|
|
|
if (!_mlyViewer) {
|
|
this.initViewer(imageKey, context);
|
|
} else {
|
|
_mlyViewer.moveToKey(imageKey);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
|
|
initViewer: function(imageKey, context) {
|
|
var that = this;
|
|
if (Mapillary && imageKey) {
|
|
var opts = {
|
|
baseImageSize: 320,
|
|
component: {
|
|
cover: false,
|
|
keyboard: false,
|
|
tag: true
|
|
}
|
|
};
|
|
|
|
_mlyViewer = new Mapillary.Viewer('mly', clientId, imageKey, opts);
|
|
_mlyViewer.on('nodechanged', nodeChanged);
|
|
}
|
|
|
|
// 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 asychronously.
|
|
//
|
|
// Clicks are added to the array in `selectedImage` and removed here.
|
|
//
|
|
function nodeChanged(node) {
|
|
_mlyViewer.getComponent('tag').removeAll(); // remove previous detections
|
|
|
|
var clicks = _mlyClicks;
|
|
var index = clicks.indexOf(node.key);
|
|
var selectedKey = _mlySelectedImage && _mlySelectedImage.key;
|
|
|
|
if (index > -1) { // `nodechanged` initiated from clicking on a marker..
|
|
clicks.splice(index, 1); // remove the click
|
|
// If `node.key` matches the current _mlySelectedImage, call `selectImage()`
|
|
// one more time to update the detections and attribution..
|
|
if (node.key === selectedKey) {
|
|
that.selectImage(_mlySelectedImage, node.key, true);
|
|
}
|
|
} else { // `nodechanged` initiated from the Mapillary viewer controls..
|
|
var loc = node.computedLatLon ? [node.computedLatLon.lon, node.computedLatLon.lat] : [node.latLon.lon, node.latLon.lat];
|
|
context.map().centerEase(loc);
|
|
that.selectImage(undefined, node.key, true);
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
// Pass the image datum itself in `d` or the `imageKey` string.
|
|
// 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(d, imageKey, fromViewer) {
|
|
if (!d && imageKey) {
|
|
// If the user clicked on something that's not an image marker, we
|
|
// might get in here.. Cache lookup can fail, e.g. if the user
|
|
// clicked a streetsign, but images are loading slowly asynchronously.
|
|
// We'll try to carry on anyway if there is no datum. There just
|
|
// might be a delay before user sees detections, captured_at, etc.
|
|
d = _mlyCache.images.forImageKey[imageKey];
|
|
}
|
|
|
|
_mlySelectedImage = d;
|
|
var viewer = d3_select('#photoviewer');
|
|
if (!viewer.empty()) viewer.datum(d);
|
|
|
|
imageKey = (d && d.key) || imageKey;
|
|
if (!fromViewer && imageKey) {
|
|
_mlyClicks.push(imageKey);
|
|
}
|
|
|
|
this.setStyles(null, true);
|
|
|
|
d3_selectAll('.layer-mapillary-signs .icon-sign')
|
|
.classed('selected', function(d) {
|
|
return _some(d.detections, function(detection) {
|
|
return detection.image_key === imageKey;
|
|
});
|
|
});
|
|
|
|
var wrap = d3_select('#photoviewer .mly-wrapper');
|
|
var attribution = wrap.selectAll('.photo-attribution').html('');
|
|
|
|
if (d) {
|
|
if (d.captured_by) {
|
|
attribution
|
|
.append('a')
|
|
.attr('class', 'captured_by')
|
|
.attr('target', '_blank')
|
|
.attr('href', 'https://www.mapillary.com/app/user/' + encodeURIComponent(d.captured_by))
|
|
.text('@' + d.captured_by);
|
|
|
|
attribution
|
|
.append('span')
|
|
.text('|');
|
|
}
|
|
|
|
if (d.captured_at) {
|
|
attribution
|
|
.append('span')
|
|
.attr('class', 'captured_at')
|
|
.text(localeTimestamp(d.captured_at));
|
|
|
|
attribution
|
|
.append('span')
|
|
.text('|');
|
|
}
|
|
|
|
attribution
|
|
.append('a')
|
|
.attr('class', 'image_link')
|
|
.attr('target', '_blank')
|
|
.attr('href', 'https://www.mapillary.com/app/?pKey=' + encodeURIComponent(d.key) +
|
|
'&focus=photo&lat=' + d.loc[1] + '&lng=' + d.loc[0] + '&z=17')
|
|
.text('mapillary.com');
|
|
|
|
this.updateDetections(d);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
|
|
getSelectedImage: function() {
|
|
return _mlySelectedImage;
|
|
},
|
|
|
|
|
|
getSequenceKeyForImage: function(d) {
|
|
var imageKey = d && d.key;
|
|
return imageKey && _mlyCache.sequences.forImageKey[imageKey];
|
|
},
|
|
|
|
|
|
setStyles: function(hovered, reset) {
|
|
if (reset) { // reset all layers
|
|
d3_selectAll('.viewfield-group')
|
|
.classed('highlighted', false)
|
|
.classed('hovered', false)
|
|
.classed('selected', false);
|
|
|
|
d3_selectAll('.sequence')
|
|
.classed('highlighted', false)
|
|
.classed('selected', false);
|
|
}
|
|
|
|
var hoveredImageKey = hovered && hovered.key;
|
|
var hoveredSequenceKey = this.getSequenceKeyForImage(hovered);
|
|
var hoveredLineString = hoveredSequenceKey && _mlyCache.sequences.lineString[hoveredSequenceKey];
|
|
var hoveredImageKeys = (hoveredLineString && hoveredLineString.properties.coordinateProperties.image_keys) || [];
|
|
|
|
var viewer = d3_select('#photoviewer');
|
|
var selected = viewer.empty() ? undefined : viewer.datum();
|
|
var selectedImageKey = selected && selected.key;
|
|
var selectedSequenceKey = this.getSequenceKeyForImage(selected);
|
|
var selectedLineString = selectedSequenceKey && _mlyCache.sequences.lineString[selectedSequenceKey];
|
|
var selectedImageKeys = (selectedLineString && selectedLineString.properties.coordinateProperties.image_keys) || [];
|
|
|
|
// highlight sibling viewfields on either the selected or the hovered sequences
|
|
var highlightedImageKeys = _union(hoveredImageKeys, selectedImageKeys);
|
|
|
|
d3_selectAll('.layer-mapillary-images .viewfield-group')
|
|
.classed('highlighted', function(d) { return highlightedImageKeys.indexOf(d.key) !== -1; })
|
|
.classed('hovered', function(d) { return d.key === hoveredImageKey; })
|
|
.classed('selected', function(d) { return d.key === selectedImageKey; });
|
|
|
|
d3_selectAll('.layer-mapillary-images .sequence')
|
|
.classed('highlighted', function(d) { return d.properties.key === hoveredSequenceKey; })
|
|
.classed('selected', function(d) { return d.properties.key === selectedSequenceKey; });
|
|
|
|
return this;
|
|
},
|
|
|
|
|
|
updateDetections: function(d) {
|
|
if (!_mlyViewer) return;
|
|
|
|
var imageKey = d && d.key;
|
|
var detections = (imageKey && _mlyCache.detections[imageKey]) || [];
|
|
|
|
_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]);
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
function loadDetection(detectionKey) {
|
|
var url = apibase + '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('--');
|
|
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') {
|
|
var polygonGeometry = new Mapillary
|
|
.TagComponent
|
|
.PolygonGeometry(data.properties.shape.coordinates[0]);
|
|
|
|
tag = new Mapillary.TagComponent.OutlineTag(
|
|
data.properties.key,
|
|
polygonGeometry,
|
|
{
|
|
text: text,
|
|
textColor: 0xffff00,
|
|
lineColor: 0xffff00,
|
|
lineWidth: 2,
|
|
fillColor: 0xffff00,
|
|
fillOpacity: 0.3,
|
|
}
|
|
);
|
|
|
|
} else if (data.properties.shape.type === 'Point') {
|
|
var pointGeometry = new Mapillary
|
|
.TagComponent
|
|
.PointGeometry(data.properties.shape.coordinates[0]);
|
|
|
|
tag = new Mapillary.TagComponent.SpotTag(
|
|
data.properties.key,
|
|
pointGeometry,
|
|
{
|
|
text: text,
|
|
color: 0xffff00,
|
|
textColor: 0xffff00
|
|
}
|
|
);
|
|
}
|
|
|
|
return tag;
|
|
}
|
|
},
|
|
|
|
|
|
cache: function() {
|
|
return _mlyCache;
|
|
},
|
|
|
|
|
|
signDefs: function(_) {
|
|
if (!arguments.length) return _mlySignDefs;
|
|
_mlySignDefs = _;
|
|
return this;
|
|
}
|
|
|
|
};
|