Highlight selected/hovered streetview tracks, fade unselected

Also some code cleanups and nitpicky variable renames
More consistency in Mapillary/OpenStreetCam services
This commit is contained in:
Bryan Housel
2017-11-08 00:35:44 -05:00
parent 9fa078b4b9
commit 4571fd0563
8 changed files with 435 additions and 246 deletions
+45 -15
View File
@@ -24,41 +24,71 @@
overflow: hidden;
}
/* markers and sequences */
.viewfield-group {
pointer-events: visible;
cursor: pointer;
}
.viewfield-group * {
stroke-width: 1;
stroke: #444;
z-index: 50;
}
.viewfield-group.selected * {
stroke-width: 2;
stroke: #222;
fill: #ff5800 !important;
fill: #ffee00 !important;
z-index: 60;
}
.viewfield-group.hovered * {
fill: #eebb00 !important;
z-index: 70;
}
.viewfield-group.highlighted * {
z-index: 60;
}
.viewfield-group:hover * {
stroke-width: 1;
.viewfield-group circle {
stroke: #333;
fill: #ff9900 !important;
z-index: 70;
stroke-width: 1;
stroke-opacity: 0.4;
fill-opacity: 0.4;
}
.viewfield-group.highlighted circle {
stroke-opacity: 0.9;
fill-opacity: 0.9;
}
.viewfield-group.highlighted.hovered circle {
stroke-width: 2;
stroke-opacity: 0.9;
fill-opacity: 0.9;
}
.viewfield-group.highlighted.selected circle {
stroke-width: 2;
stroke-opacity: 1;
fill-opacity: 1;
}
.viewfield-group:hover path.viewfield,
.viewfield-group.selected path.viewfield,
.viewfield-group path.viewfield {
.viewfield-group .viewfield {
stroke-width: 0;
fill-opacity: 0.6;
fill-opacity: 0.4;
}
.viewfield-group.highlighted .viewfield {
fill-opacity: 0.8;
}
.viewfield-group.highlighted.hovered .viewfield {
fill-opacity: 0.8;
}
.viewfield-group.highlighted.selected .viewfield {
fill-opacity: 0.9;
}
.sequence {
stroke-width: 2;
fill: none;
stroke-width: 2;
stroke-opacity: 0.4;
}
.sequence.highlighted,
.sequence.selected {
stroke-width: 4;
stroke-opacity: 1;
}
+150 -89
View File
@@ -6,6 +6,7 @@ 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';
@@ -35,12 +36,12 @@ var apibase = 'https://a.mapillary.com/v3/',
maxResults = 1000,
tileZoom = 14,
dispatch = d3_dispatch('loadedImages', 'loadedSigns'),
mapillaryCache,
mapillaryClicks,
mapillaryImage,
mapillarySignDefs,
mapillarySignSprite,
mapillaryViewer;
_mlyCache,
_mlyClicks,
_mlySelectedImage,
_mlySignDefs,
_mlySignSprite,
_mlyViewer;
function abortRequest(i) {
@@ -120,7 +121,7 @@ function loadTiles(which, url, projection) {
function loadNextTilePage(which, currZoom, url, tile) {
var cache = mapillaryCache[which],
var cache = _mlyCache[which],
rect = tile.extent.rectangle(),
maxPages = maxPageAtZoom(currZoom),
nextPage = cache.nextPage[tile.id] || 0,
@@ -165,14 +166,15 @@ function loadNextTilePage(which, currZoom, url, tile) {
captured_at: feature.properties.captured_at,
pano: feature.properties.pano
};
cache.forImageKey[d.key] = d; // cache imageKey -> image
} else if (which === 'sequences') {
var sk = feature.properties.key;
cache.lineString[sk] = feature; // cache sequence_key -> linestring
feature.properties.coordinateProperties.image_keys.forEach(function(ik) {
cache.forImage[ik] = sk; // cache image_key -> sequence_key
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; // nothing to actually insert
return false; // because no `d` data worth loading into an rbush
} else if (which === 'objects') {
d = {
@@ -183,15 +185,15 @@ function loadNextTilePage(which, currZoom, url, tile) {
detections: feature.properties.detections
};
// cache image_key -> detection_key
// cache imageKey -> detectionKey
feature.properties.detections.forEach(function(detection) {
var ik = detection.image_key;
var dk = detection.detection_key;
if (!mapillaryCache.detections[ik]) {
mapillaryCache.detections[ik] = {};
var imageKey = detection.image_key;
var detectionKey = detection.detection_key;
if (!_mlyCache.detections[imageKey]) {
_mlyCache.detections[imageKey] = {};
}
if (!mapillaryCache.detections[ik][dk]) {
mapillaryCache.detections[ik][dk] = {};
if (!_mlyCache.detections[imageKey][detectionKey]) {
_mlyCache.detections[imageKey][detectionKey] = {};
}
});
}
@@ -199,6 +201,7 @@ function loadNextTilePage(which, currZoom, url, tile) {
return {
minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d
};
}).filter(Boolean);
cache.rtree.load(features);
@@ -295,7 +298,7 @@ function searchLimited(psize, limit, projection, rtree) {
export default {
init: function() {
if (!mapillaryCache) {
if (!_mlyCache) {
this.reset();
}
@@ -303,7 +306,7 @@ export default {
},
reset: function() {
var cache = mapillaryCache;
var cache = _mlyCache;
if (cache) {
if (cache.images && cache.images.inflight) {
@@ -317,27 +320,27 @@ export default {
}
}
mapillaryCache = {
images: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush() },
_mlyCache = {
images: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush(), forImageKey: {} },
objects: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush() },
sequences: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush(), forImage: {}, lineString: {} },
sequences: { inflight: {}, loaded: {}, nextPage: {}, nextURL: {}, rtree: rbush(), forImageKey: {}, lineString: {} },
detections: {}
};
mapillaryImage = null;
mapillaryClicks = [];
_mlySelectedImage = null;
_mlyClicks = [];
},
images: function(projection) {
var psize = 16, limit = 3;
return searchLimited(psize, limit, projection, mapillaryCache.images.rtree);
return searchLimited(psize, limit, projection, _mlyCache.images.rtree);
},
signs: function(projection) {
var psize = 32, limit = 3;
return searchLimited(psize, limit, projection, mapillaryCache.objects.rtree);
return searchLimited(psize, limit, projection, _mlyCache.objects.rtree);
},
@@ -349,17 +352,17 @@ export default {
var sequenceKeys = {};
// all sequences for images in viewport
mapillaryCache.images.rtree.search(bbox)
_mlyCache.images.rtree.search(bbox)
.forEach(function(d) {
var sk = mapillaryCache.sequences.forImage[d.data.key];
if (sk) {
sequenceKeys[sk] = true;
var sequenceKey = _mlyCache.sequences.forImageKey[d.data.key];
if (sequenceKey) {
sequenceKeys[sequenceKey] = true;
}
});
// Return linestrings for the sequences
return Object.keys(sequenceKeys).map(function(sk) {
return mapillaryCache.sequences.lineString[sk];
// Return lineStrings for the sequences
return Object.keys(sequenceKeys).map(function(sequenceKey) {
return _mlyCache.sequences.lineString[sequenceKey];
});
},
@@ -373,11 +376,11 @@ export default {
signHTML: function(d) {
if (!mapillarySignDefs || !mapillarySignSprite) return;
var position = mapillarySignDefs[d.value];
if (!_mlySignDefs || !_mlySignSprite) return;
var position = _mlySignDefs[d.value];
if (!position) return '<div></div>';
var iconStyle = [
'background-image:url(' + mapillarySignSprite + ')',
'background-image:url(' + _mlySignSprite + ')',
'background-repeat:no-repeat',
'height:' + position.height + 'px',
'width:' + position.width + 'px',
@@ -400,12 +403,12 @@ export default {
loadTiles('objects', url, projection);
// load traffic sign defs
if (!mapillarySignDefs) {
mapillarySignSprite = context.asset('img/traffic-signs/traffic-signs.png');
mapillarySignDefs = {};
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;
mapillarySignDefs = data;
_mlySignDefs = data;
});
}
},
@@ -455,7 +458,7 @@ export default {
.selectAll('.photo-wrapper.mly-wrapper')
.classed('hide', false);
mapillaryViewer.resize();
_mlyViewer.resize();
}
return this;
@@ -468,10 +471,10 @@ export default {
.selectAll('.photo-wrapper')
.classed('hide', true);
d3_selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
d3_selectAll('.viewfield-group, .sequence, .icon-sign')
.classed('selected', false);
mapillaryImage = null;
_mlySelectedImage = null;
return this;
},
@@ -479,13 +482,13 @@ export default {
parsePagination: parsePagination,
updateViewer: function(imageKey, context) {
if (!imageKey) return;
updateViewer: function(d, context) {
if (!d || !d.key) return;
if (!mapillaryViewer) {
this.initViewer(imageKey, context);
if (!_mlyViewer) {
this.initViewer(d.key, context);
} else {
mapillaryViewer.moveToKey(imageKey);
_mlyViewer.moveToKey(d.key);
}
return this;
@@ -504,51 +507,54 @@ export default {
}
};
mapillaryViewer = new Mapillary.Viewer('mly', clientId, imageKey, opts);
mapillaryViewer.on('nodechanged', nodeChanged);
_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 mapillaryClicks array
// 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) {
mapillaryViewer.getComponent('tag').removeAll(); // remove previous detections
_mlyViewer.getComponent('tag').removeAll(); // remove previous detections
var clicks = mapillaryClicks;
var clicks = _mlyClicks;
var index = clicks.indexOf(node.key);
if (index > -1) { // `nodechanged` initiated from clicking on a marker..
clicks.splice(index, 1);
// If `node.key` matches the current mapillaryImage, call `selectedImage()`
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 === mapillaryImage) {
that.selectedImage(node.key, false);
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.selectedImage(node.key, false);
that.selectImage(undefined, node.key, true);
}
}
},
selectedImage: function(imageKey, fromClick) {
if (!arguments.length) return mapillaryImage;
mapillaryImage = imageKey;
if (fromClick) {
mapillaryClicks.push(imageKey);
selectImage: function(d, imageKey, fromViewer) {
if (!d && imageKey) {
d = _mlyCache.images.forImageKey[imageKey];
}
d3_selectAll('.viewfield-group')
.classed('selected', function(d) {
return d.key === imageKey;
});
_mlySelectedImage = d;
imageKey = d && d.key;
if (!fromViewer && imageKey) {
_mlyClicks.push(imageKey);
}
this.setStyles(null, _mlySelectedImage, true);
d3_selectAll('.layer-mapillary-signs .icon-sign')
.classed('selected', function(d) {
@@ -557,7 +563,7 @@ export default {
});
});
if (!imageKey) return this;
if (!d) return this;
function localeTimestamp(s) {
@@ -570,14 +576,14 @@ export default {
var selected = d3_selectAll('.layer-mapillary-images .viewfield-group.selected');
if (selected.empty()) return this;
var datum = selected.datum();
var timestamp = localeTimestamp(datum.captured_at);
var timestamp = localeTimestamp(d.captured_at);
var attribution = d3_select('.mapillary-js-dom .Attribution');
var capturedAt = attribution.selectAll('.captured-at');
if (capturedAt.empty()) {
capturedAt = attribution
.insert('span', ':last-child')
.attr('class', 'captured-at');
attribution
.insert('span', ':last-child')
.text('|');
@@ -585,23 +591,79 @@ export default {
capturedAt
.text(timestamp);
this.updateDetections();
this.updateDetections(d);
return this;
},
updateDetections: function() {
if (!mapillaryViewer) return;
getSelectedImage: function() {
return _mlySelectedImage;
},
getSequenceKeyForImage: function(d) {
var imageKey = d && d.key;
return imageKey && _mlyCache.sequences.forImageKey[imageKey];
},
getSelectedSequenceKey: function() {
return this.getSequenceKeyForImage(_mlySelectedImage);
},
setStyles: function(hovered, selected, 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 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]) || [];
var detections = mapillaryCache.detections[mapillaryImage];
_forEach(detections, function(data, k) {
if (_isEmpty(data)) {
loadDetection(k);
} else {
var tag = makeTag(data);
if (tag) {
var tagComponent = mapillaryViewer.getComponent('tag');
var tagComponent = _mlyViewer.getComponent('tag');
tagComponent.add([tag]);
}
}
@@ -622,13 +684,14 @@ export default {
.get(function(err, data) {
if (!data || !data.properties) return;
var ik = data.properties.image_key;
mapillaryCache.detections[ik][detectionKey] = data;
var imageKey = data.properties.image_key;
_mlyCache.detections[imageKey][detectionKey] = data;
if (mapillaryImage === ik) {
var selectedKey = _mlySelectedImage && _mlySelectedImage.key;
if (imageKey === selectedKey) {
var tag = makeTag(data);
if (tag) {
var tagComponent = mapillaryViewer.getComponent('tag');
var tagComponent = _mlyViewer.getComponent('tag');
tagComponent.add([tag]);
}
}
@@ -683,16 +746,14 @@ export default {
},
cache: function(_) {
if (!arguments.length) return mapillaryCache;
mapillaryCache = _;
return this;
cache: function() {
return _mlyCache;
},
signDefs: function(_) {
if (!arguments.length) return mapillarySignDefs;
mapillarySignDefs = _;
if (!arguments.length) return _mlySignDefs;
_mlySignDefs = _;
return this;
}
+104 -51
View File
@@ -3,6 +3,7 @@ import _find from 'lodash-es/find';
import _flatten from 'lodash-es/flatten';
import _forEach from 'lodash-es/forEach';
import _map from 'lodash-es/map';
import _union from 'lodash-es/union';
import { range as d3_range } from 'd3-array';
import { dispatch as d3_dispatch } from 'd3-dispatch';
@@ -24,8 +25,8 @@ var apibase = 'http://openstreetcam.org',
maxResults = 1000,
tileZoom = 14,
dispatch = d3_dispatch('loadedImages'),
openstreetcamCache,
openstreetcamImage;
_oscCache,
_oscSelectedImage;
function abortRequest(i) {
@@ -105,7 +106,7 @@ function loadTiles(which, url, projection) {
function loadNextTilePage(which, currZoom, url, tile) {
var cache = openstreetcamCache[which];
var cache = _oscCache[which];
var bbox = tile.extent.bbox();
var maxPages = maxPageAtZoom(currZoom);
var nextPage = cache.nextPage[tile.id] || 1;
@@ -150,15 +151,15 @@ function loadNextTilePage(which, currZoom, url, tile) {
captured_at: localeDateString(item.shot_date || item.date_added),
captured_by: item.username,
imagePath: item.lth_name,
sequence_id: +item.sequence_id,
sequence_id: item.sequence_id,
sequence_index: +item.sequence_index
};
// cache sequence info
var seq = openstreetcamCache.sequences[d.sequence_id];
var seq = _oscCache.sequences[d.sequence_id];
if (!seq) {
seq = { rotation: 0, images: [] };
openstreetcamCache.sequences[d.sequence_id] = seq;
_oscCache.sequences[d.sequence_id] = seq;
}
seq.images[d.sequence_index] = d;
}
@@ -225,7 +226,7 @@ function searchLimited(psize, limit, projection, rtree) {
export default {
init: function() {
if (!openstreetcamCache) {
if (!_oscCache) {
this.reset();
}
@@ -233,7 +234,7 @@ export default {
},
reset: function() {
var cache = openstreetcamCache;
var cache = _oscCache;
if (cache) {
if (cache.images && cache.images.inflight) {
@@ -241,18 +242,18 @@ export default {
}
}
openstreetcamCache = {
_oscCache = {
images: { inflight: {}, loaded: {}, nextPage: {}, rtree: rbush() },
sequences: {}
};
openstreetcamImage = null;
_oscSelectedImage = null;
},
images: function(projection) {
var psize = 16, limit = 3;
return searchLimited(psize, limit, projection, openstreetcamCache.images.rtree);
return searchLimited(psize, limit, projection, _oscCache.images.rtree);
},
@@ -261,24 +262,26 @@ export default {
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 seq_ids = {};
var sequenceKeys = {};
// all sequences for images in viewport
openstreetcamCache.images.rtree.search(bbox)
.forEach(function(d) { seq_ids[d.data.sequence_id] = true; });
_oscCache.images.rtree.search(bbox)
.forEach(function(d) { sequenceKeys[d.data.sequence_id] = true; });
// make linestrings from those sequences
var lineStrings = [];
Object.keys(seq_ids).forEach(function(seq_id) {
var seq = openstreetcamCache.sequences[seq_id];
var images = seq && seq.images;
if (images) {
lineStrings.push({
type: 'LineString',
coordinates: images.map(function (d) { return d.loc; }).filter(Boolean)
});
}
});
Object.keys(sequenceKeys)
.forEach(function(sequenceKey) {
var seq = _oscCache.sequences[sequenceKey];
var images = seq && seq.images;
if (images) {
lineStrings.push({
type: 'LineString',
coordinates: images.map(function (d) { return d.loc; }).filter(Boolean),
properties: { key: sequenceKey }
});
}
});
return lineStrings;
},
@@ -334,14 +337,14 @@ export default {
function rotate(deg) {
return function() {
if (!openstreetcamImage) return;
var seq_id = openstreetcamImage.sequence_id;
var seq = openstreetcamCache.sequences[seq_id];
if (!seq) return;
if (!_oscSelectedImage) return;
var sequenceKey = _oscSelectedImage.sequence_id;
var sequence = _oscCache.sequences[sequenceKey];
if (!sequence) return;
var r = seq.rotation || 0;
var r = sequence.rotation || 0;
r += deg;
seq.rotation = r;
sequence.rotation = r;
d3_select('#photoviewer .osc-wrapper .osc-image')
.transition()
@@ -352,19 +355,19 @@ export default {
function step(stepBy) {
return function() {
if (!openstreetcamImage) return;
var seq_id = openstreetcamImage.sequence_id;
var seq = openstreetcamCache.sequences[seq_id];
if (!seq) return;
if (!_oscSelectedImage) return;
var sequenceKey = _oscSelectedImage.sequence_id;
var sequence = _oscCache.sequences[sequenceKey];
if (!sequence) return;
var nextIndex = openstreetcamImage.sequence_index + stepBy;
var nextImage = seq.images[nextIndex];
var nextIndex = _oscSelectedImage.sequence_index + stepBy;
var nextImage = sequence.images[nextIndex];
if (!nextImage) return;
context.map().centerEase(nextImage.loc);
that
.selectedImage(nextImage)
.selectImage(nextImage)
.updateViewer(nextImage);
};
}
@@ -397,10 +400,10 @@ export default {
.selectAll('.photo-wrapper')
.classed('hide', true);
d3_selectAll('.layer-openstreetcam-images .viewfield-group')
d3_selectAll('.viewfield-group, .sequence, .icon-sign')
.classed('selected', false);
openstreetcamImage = null;
_oscSelectedImage = null;
return this;
},
@@ -412,8 +415,8 @@ export default {
.remove();
if (d) {
var seq = openstreetcamCache.sequences[d.sequence_id];
var r = (seq && seq.rotation) || 0;
var sequence = _oscCache.sequences[d.sequence_id];
var r = (sequence && sequence.rotation) || 0;
wrap.append('img')
.attr('class', 'osc-image')
@@ -457,23 +460,73 @@ export default {
},
selectedImage: function(d) {
if (!arguments.length) return openstreetcamImage;
openstreetcamImage = d;
selectImage: function(d) {
_oscSelectedImage = d;
d3_selectAll('.viewfield-group')
.classed('selected', function(d) {
return d.key === openstreetcamImage.key;
});
this.setStyles(null, _oscSelectedImage, true);
d3_selectAll('.icon-sign')
.classed('selected', false);
return this;
},
cache: function(_) {
if (!arguments.length) return openstreetcamCache;
openstreetcamCache = _;
getSelectedImage: function() {
return _oscSelectedImage;
},
getSequenceKeyForImage: function(d) {
return d && d.sequence_id;
},
getSelectedSequenceKey: function() {
return this.getSequenceKeyForImage(_oscSelectedImage);
},
setStyles: function(hovered, selected, 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 hoveredSequence = hoveredSequenceKey && _oscCache.sequences[hoveredSequenceKey];
var hoveredImageKeys = (hoveredSequence && hoveredSequence.images.map(function (d) { return d.key; })) || [];
var selectedImageKey = selected && selected.key;
var selectedSequenceKey = this.getSequenceKeyForImage(selected);
var selectedSequence = selectedSequenceKey && _oscCache.sequences[selectedSequenceKey];
var selectedImageKeys = (selectedSequence && selectedSequence.images.map(function (d) { return d.key; })) || [];
// highlight sibling viewfields on either the selected or the hovered sequences
var highlightedImageKeys = _union(hoveredImageKeys, selectedImageKeys);
d3_selectAll('.layer-openstreetcam-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-openstreetcam-images .sequence')
.classed('highlighted', function(d) { return d.properties.key === hoveredSequenceKey; })
.classed('selected', function(d) { return d.properties.key === selectedSequenceKey; });
return this;
},
cache: function() {
return _oscCache;
}
};
+43 -21
View File
@@ -14,7 +14,7 @@ import { services } from '../services';
export function svgMapillaryImages(projection, context, dispatch) {
var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000),
minZoom = 12,
minViewfieldZoom = 17,
minViewfieldZoom = 18,
layer = d3_select(null),
_mapillary;
@@ -26,7 +26,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
}
function getMapillary() {
function getService() {
if (services.mapillary && !_mapillary) {
_mapillary = services.mapillary;
_mapillary.event.on('loadedImages', throttledRedraw);
@@ -39,10 +39,10 @@ export function svgMapillaryImages(projection, context, dispatch) {
function showLayer() {
var mapillary = getMapillary();
if (!mapillary) return;
var service = getService();
if (!service) return;
mapillary.loadViewer(context);
service.loadViewer(context);
editOn();
layer
@@ -55,9 +55,9 @@ export function svgMapillaryImages(projection, context, dispatch) {
function hideLayer() {
var mapillary = getMapillary();
if (mapillary) {
mapillary.hideViewer();
var service = getService();
if (service) {
service.hideViewer();
}
throttledRedraw.cancel();
@@ -82,18 +82,34 @@ export function svgMapillaryImages(projection, context, dispatch) {
function click(d) {
var mapillary = getMapillary();
if (!mapillary) return;
var service = getService();
if (!service) return;
context.map().centerEase(d.loc);
mapillary
.selectedImage(d.key, true)
.updateViewer(d.key, context)
service
.selectImage(d)
.updateViewer(d, context)
.showViewer();
}
function mouseover(d) {
var service = getService();
var selected = d3_select('.viewfield-group.selected');
var datum = selected.size() && selected.datum();
if (service) service.setStyles(d, datum);
}
function mouseout() {
var service = getService();
var selected = d3_select('.viewfield-group.selected');
var datum = selected.size() && selected.datum();
if (service) service.setStyles(null, datum);
}
function transform(d) {
var t = svgPointTransform(projection)(d);
if (d.ca) t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
@@ -103,10 +119,9 @@ export function svgMapillaryImages(projection, context, dispatch) {
function update() {
var highZoom = ~~context.map().zoom() >= minViewfieldZoom;
var mapillary = getMapillary();
var images = (mapillary ? mapillary.images(projection) : []);
var sequences = (mapillary && highZoom ? mapillary.sequences(projection) : []);
var imageKey = mapillary ? mapillary.selectedImage() : null;
var service = getService();
var images = (service ? service.images(projection) : []);
var sequences = (service && highZoom ? service.sequences(projection) : []);
var clip = d3_geoIdentity().clipExtent(projection.clipExtent()).stream;
var project = projection.stream;
@@ -115,7 +130,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
}});
var lineStrings = layer.selectAll('.sequences').selectAll('.sequence')
.data(sequences);
.data(sequences, function(d) { return d.properties.key; });
lineStrings.exit()
.remove();
@@ -138,7 +153,8 @@ export function svgMapillaryImages(projection, context, dispatch) {
var enter = markers.enter()
.append('g')
.attr('class', 'viewfield-group')
.classed('selected', function(d) { return d.key === imageKey; })
.on('mouseover', mouseover)
.on('mouseout', mouseout)
.on('click', click);
markers = markers
@@ -166,6 +182,12 @@ export function svgMapillaryImages(projection, context, dispatch) {
.attr('dy', '0')
.attr('r', '6');
var selected = d3_select('.viewfield-group.selected');
var datum = selected.size() && selected.datum();
if (service) service.setStyles(null, datum);
function viewfieldPath() {
var d = this.parentNode.__data__;
if (d.pano) {
@@ -179,7 +201,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
function drawImages(selection) {
var enabled = svgMapillaryImages.enabled,
mapillary = getMapillary();
mapillary = getService();
layer = selection.selectAll('.layer-mapillary-images')
.data(mapillary ? [0] : []);
@@ -229,7 +251,7 @@ export function svgMapillaryImages(projection, context, dispatch) {
drawImages.supported = function() {
return !!getMapillary();
return !!getService();
};
+17 -16
View File
@@ -18,7 +18,7 @@ export function svgMapillarySigns(projection, context, dispatch) {
}
function getMapillary() {
function getService() {
if (services.mapillary && !_mapillary) {
_mapillary = services.mapillary;
_mapillary.event.on('loadedSigns', throttledRedraw);
@@ -52,12 +52,12 @@ export function svgMapillarySigns(projection, context, dispatch) {
function click(d) {
var mapillary = getMapillary();
if (!mapillary) return;
var service = getService();
if (!service) return;
context.map().centerEase(d.loc);
var selected = mapillary.selectedImage(),
var selected = service.selectedImage(),
imageKey;
// Pick one of the images the sign was detected in,
@@ -68,17 +68,18 @@ export function svgMapillarySigns(projection, context, dispatch) {
}
});
mapillary
.selectedImage(imageKey, true)
service
.selectImage(null, imageKey)
.updateViewer(imageKey, context)
.showViewer();
}
function update() {
var mapillary = getMapillary(),
data = (mapillary ? mapillary.signs(projection) : []),
imageKey = mapillary ? mapillary.selectedImage() : null;
var service = getService();
var data = (service ? service.signs(projection) : []);
var image = service && service.getSelectedImage();
var imageKey = image && image.key;
var signs = layer.selectAll('.icon-sign')
.data(data, function(d) { return d.key; });
@@ -101,7 +102,7 @@ export function svgMapillarySigns(projection, context, dispatch) {
enter
.append('xhtml:body')
.attr('class', 'icon-sign-body')
.html(mapillary.signHTML);
.html(service.signHTML);
signs
.merge(enter)
@@ -112,10 +113,10 @@ export function svgMapillarySigns(projection, context, dispatch) {
function drawSigns(selection) {
var enabled = svgMapillarySigns.enabled,
mapillary = getMapillary();
service = getService();
layer = selection.selectAll('.layer-mapillary-signs')
.data(mapillary ? [0] : []);
.data(service ? [0] : []);
layer.exit()
.remove();
@@ -127,10 +128,10 @@ export function svgMapillarySigns(projection, context, dispatch) {
.merge(layer);
if (enabled) {
if (mapillary && ~~context.map().zoom() >= minZoom) {
if (service && ~~context.map().zoom() >= minZoom) {
editOn();
update();
mapillary.loadSigns(context, projection);
service.loadSigns(context, projection);
} else {
editOff();
}
@@ -152,8 +153,8 @@ export function svgMapillarySigns(projection, context, dispatch) {
drawSigns.supported = function() {
var mapillary = getMapillary();
return (mapillary && mapillary.signsSupported());
var service = getService();
return (service && service.signsSupported());
};
+46 -25
View File
@@ -1,11 +1,12 @@
import _throttle from 'lodash-es/throttle';
import { select as d3_select } from 'd3-selection';
import {
geoIdentity as d3_geoIdentity,
geoPath as d3_geoPath
} from 'd3-geo';
import { select as d3_select } from 'd3-selection';
import { svgPointTransform } from './point_transform';
import { services } from '../services';
@@ -13,7 +14,7 @@ import { services } from '../services';
export function svgOpenstreetcamImages(projection, context, dispatch) {
var throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000),
minZoom = 12,
minViewfieldZoom = 17,
minViewfieldZoom = 18,
layer = d3_select(null),
_openstreetcam;
@@ -25,7 +26,7 @@ export function svgOpenstreetcamImages(projection, context, dispatch) {
}
function getOpenstreetcam() {
function getService() {
if (services.openstreetcam && !_openstreetcam) {
_openstreetcam = services.openstreetcam;
_openstreetcam.event.on('loadedImages', throttledRedraw);
@@ -38,10 +39,10 @@ export function svgOpenstreetcamImages(projection, context, dispatch) {
function showLayer() {
var openstreetcam = getOpenstreetcam();
if (!openstreetcam) return;
var service = getService();
if (!service) return;
openstreetcam.loadViewer(context);
service.loadViewer(context);
editOn();
layer
@@ -54,9 +55,9 @@ export function svgOpenstreetcamImages(projection, context, dispatch) {
function hideLayer() {
var openstreetcam = getOpenstreetcam();
if (openstreetcam) {
openstreetcam.hideViewer();
var service = getService();
if (service) {
service.hideViewer();
}
throttledRedraw.cancel();
@@ -81,18 +82,34 @@ export function svgOpenstreetcamImages(projection, context, dispatch) {
function click(d) {
var openstreetcam = getOpenstreetcam();
if (!openstreetcam) return;
var service = getService();
if (!service) return;
context.map().centerEase(d.loc);
openstreetcam
.selectedImage(d)
service
.selectImage(d)
.updateViewer(d)
.showViewer();
}
function mouseover(d) {
var service = getService();
var selected = d3_select('.viewfield-group.selected');
var datum = selected.size() && selected.datum();
if (service) service.setStyles(d, datum);
}
function mouseout() {
var service = getService();
var selected = d3_select('.viewfield-group.selected');
var datum = selected.size() && selected.datum();
if (service) service.setStyles(null, datum);
}
function transform(d) {
var t = svgPointTransform(projection)(d);
if (d.ca) t += ' rotate(' + Math.floor(d.ca) + ',0,0)';
@@ -102,11 +119,9 @@ export function svgOpenstreetcamImages(projection, context, dispatch) {
function update() {
var highZoom = ~~context.map().zoom() >= minViewfieldZoom;
var openstreetcam = getOpenstreetcam();
var sequences = (openstreetcam && highZoom ? openstreetcam.sequences(projection) : []);
var images = (openstreetcam ? openstreetcam.images(projection) : []);
var selectedImage = openstreetcam && openstreetcam.selectedImage();
var imageKey = selectedImage && selectedImage.key;
var service = getService();
var sequences = (service && highZoom ? service.sequences(projection) : []);
var images = (service ? service.images(projection) : []);
var clip = d3_geoIdentity().clipExtent(projection.clipExtent()).stream;
var project = projection.stream;
@@ -115,7 +130,7 @@ export function svgOpenstreetcamImages(projection, context, dispatch) {
}});
var lineStrings = layer.selectAll('.sequences').selectAll('.sequence')
.data(sequences);
.data(sequences, function(d) { return d.properties.key; });
lineStrings.exit()
.remove();
@@ -138,7 +153,8 @@ export function svgOpenstreetcamImages(projection, context, dispatch) {
var enter = markers.enter()
.append('g')
.attr('class', 'viewfield-group')
.classed('selected', function(d) { return d.key === imageKey; })
.on('mouseover', mouseover)
.on('mouseout', mouseout)
.on('click', click);
markers = markers
@@ -165,15 +181,20 @@ export function svgOpenstreetcamImages(projection, context, dispatch) {
.attr('dx', '0')
.attr('dy', '0')
.attr('r', '6');
var selected = d3_select('.viewfield-group.selected');
var datum = selected.size() && selected.datum();
if (service) service.setStyles(null, datum);
}
function drawImages(selection) {
var enabled = svgOpenstreetcamImages.enabled,
openstreetcam = getOpenstreetcam();
service = getService();
layer = selection.selectAll('.layer-openstreetcam-images')
.data(openstreetcam ? [0] : []);
.data(service ? [0] : []);
layer.exit()
.remove();
@@ -195,10 +216,10 @@ export function svgOpenstreetcamImages(projection, context, dispatch) {
.merge(layer);
if (enabled) {
if (openstreetcam && ~~context.map().zoom() >= minZoom) {
if (service && ~~context.map().zoom() >= minZoom) {
editOn();
update();
openstreetcam.loadImages(projection);
service.loadImages(projection);
} else {
editOff();
}
@@ -220,7 +241,7 @@ export function svgOpenstreetcamImages(projection, context, dispatch) {
drawImages.supported = function() {
return !!getOpenstreetcam();
return !!getService();
};
+10 -10
View File
@@ -65,12 +65,12 @@ describe('iD.serviceMapillary', function() {
describe('#reset', function() {
it('resets cache and image', function() {
mapillary.cache({foo: 'bar'});
mapillary.selectedImage('baz');
mapillary.cache().foo = 'bar';
mapillary.selectImage({key: 'baz'});
mapillary.reset();
expect(mapillary.cache()).to.not.have.property('foo');
expect(mapillary.selectedImage()).to.be.null;
expect(mapillary.getSelectedImage()).to.be.null;
});
});
@@ -377,9 +377,9 @@ describe('iD.serviceMapillary', function() {
};
mapillary.cache().sequences.lineString['-'] = gj;
mapillary.cache().sequences.forImage['0'] = '-';
mapillary.cache().sequences.forImage['1'] = '-';
mapillary.cache().sequences.forImage['2'] = '-';
mapillary.cache().sequences.forImageKey['0'] = '-';
mapillary.cache().sequences.forImageKey['1'] = '-';
mapillary.cache().sequences.forImageKey['2'] = '-';
var res = mapillary.sequences(context.projection);
expect(res).to.deep.eql([gj]);
@@ -430,10 +430,10 @@ describe('iD.serviceMapillary', function() {
});
});
describe('#selectedImage', function() {
it('sets and gets selected image', function() {
mapillary.selectedImage('foo');
expect(mapillary.selectedImage()).to.eql('foo');
describe('#selectImage', function() {
it('gets and sets the selected image', function() {
mapillary.selectImage('foo');
expect(mapillary.getSelectedImage()).to.eql('foo');
});
});
+20 -19
View File
@@ -62,12 +62,12 @@ describe('iD.serviceOpenstreetcam', function() {
describe('#reset', function() {
it('resets cache and image', function() {
openstreetcam.cache({foo: 'bar'});
openstreetcam.selectedImage('baz');
openstreetcam.cache().foo = 'bar';
openstreetcam.selectImage({key: 'baz'});
openstreetcam.reset();
expect(openstreetcam.cache()).to.not.have.property('foo');
expect(openstreetcam.selectedImage()).to.be.null;
expect(openstreetcam.getSelectedImage()).to.be.null;
});
});
@@ -251,27 +251,27 @@ describe('iD.serviceOpenstreetcam', function() {
describe('#images', function() {
it('returns images in the visible map area', function() {
var features = [
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 0 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 1 } },
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], ca: 90, sequence_id: 100, sequence_index: 2 } }
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 0 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 1 } },
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], ca: 90, sequence_id: '100', sequence_index: 2 } }
];
openstreetcam.cache().images.rtree.load(features);
var res = openstreetcam.images(context.projection);
expect(res).to.deep.eql([
{ key: '0', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 0 },
{ key: '1', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 1 }
{ key: '0', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 0 },
{ key: '1', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 1 }
]);
});
it('limits results no more than 3 stacked images in one spot', function() {
var features = [
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 0 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 1 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '2', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 2 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '3', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 3 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 4 } }
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 0 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 1 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '2', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 2 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '3', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 3 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 4 } }
];
openstreetcam.cache().images.rtree.load(features);
@@ -284,9 +284,9 @@ describe('iD.serviceOpenstreetcam', 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, sequence_id: 100, sequence_index: 0 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90, sequence_id: 100, sequence_index: 1 } },
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], ca: 90, sequence_id: 100, sequence_index: 2 } }
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 0 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 1 } },
{ minX: 10, minY: 1, maxX: 10, maxY: 1, data: { key: '2', loc: [10,1], ca: 90, sequence_id: '100', sequence_index: 2 } }
];
openstreetcam.cache().images.rtree.load(features);
@@ -295,15 +295,16 @@ describe('iD.serviceOpenstreetcam', function() {
var res = openstreetcam.sequences(context.projection);
expect(res).to.deep.eql([{
type: 'LineString',
coordinates: [[10,0], [10,0], [10,1]]
coordinates: [[10,0], [10,0], [10,1]],
properties: { key: '100' }
}]);
});
});
describe('#selectedImage', function() {
it('sets and gets selected image', function() {
openstreetcam.selectedImage('foo');
expect(openstreetcam.selectedImage()).to.eql('foo');
openstreetcam.selectImage('foo');
expect(openstreetcam.getSelectedImage()).to.eql('foo');
});
});