modules/renderer/photos.js (193 lines of code) (raw):
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { utilQsString, utilStringQs } from '@id-sdk/util';
import { services } from '../services';
import { utilRebind } from '../util/rebind';
export function rendererPhotos(context) {
var dispatch = d3_dispatch('change');
var _layerIDs = ['streetside', 'mapillary', 'mapillary-map-features', 'mapillary-signs', 'openstreetcam'];
var _allPhotoTypes = ['flat', 'panoramic'];
var _shownPhotoTypes = _allPhotoTypes.slice(); // shallow copy
var _dateFilters = ['fromDate', 'toDate'];
var _fromDate;
var _toDate;
var _usernames;
function photos() {}
function updateStorage() {
if (window.mocha) return;
var hash = utilStringQs(window.location.hash);
var enabled = context.layers().all().filter(function(d) {
return _layerIDs.indexOf(d.id) !== -1 && d.layer && d.layer.supported() && d.layer.enabled();
}).map(function(d) {
return d.id;
});
if (enabled.length) {
hash.photo_overlay = enabled.join(',');
} else {
delete hash.photo_overlay;
}
window.location.replace('#' + utilQsString(hash, true));
}
photos.overlayLayerIDs = function() {
return _layerIDs;
};
photos.allPhotoTypes = function() {
return _allPhotoTypes;
};
photos.dateFilters = function() {
return _dateFilters;
};
photos.dateFilterValue = function(val) {
return val === _dateFilters[0] ? _fromDate : _toDate;
};
photos.setDateFilter = function(type, val, updateUrl) {
// validate the date
var date = val && new Date(val);
if (date && !isNaN(date)) {
val = date.toISOString().substr(0, 10);
} else {
val = null;
}
if (type === _dateFilters[0]) {
_fromDate = val;
if (_fromDate && _toDate && new Date(_toDate) < new Date(_fromDate)) {
_toDate = _fromDate;
}
}
if (type === _dateFilters[1]) {
_toDate = val;
if (_fromDate && _toDate && new Date(_toDate) < new Date(_fromDate)) {
_fromDate = _toDate;
}
}
dispatch.call('change', this);
if (updateUrl) {
var rangeString;
if (_fromDate || _toDate) {
rangeString = (_fromDate || '') + '_' + (_toDate || '');
}
setUrlFilterValue('photo_dates', rangeString);
}
};
photos.setUsernameFilter = function(val, updateUrl) {
if (val && typeof val === 'string') val = val.replace(/;/g, ',').split(',');
if (val) {
val = val.map(d => d.trim()).filter(Boolean);
if (!val.length) {
val = null;
}
}
_usernames = val;
dispatch.call('change', this);
if (updateUrl) {
var hashString;
if (_usernames) {
hashString = _usernames.join(',');
}
setUrlFilterValue('photo_username', hashString);
}
};
function setUrlFilterValue(property, val) {
if (!window.mocha) {
var hash = utilStringQs(window.location.hash);
if (val) {
if (hash[property] === val) return;
hash[property] = val;
} else {
if (!(property in hash)) return;
delete hash[property];
}
window.location.replace('#' + utilQsString(hash, true));
}
}
function showsLayer(id) {
var layer = context.layers().layer(id);
return layer && layer.supported() && layer.enabled();
}
photos.shouldFilterByDate = function() {
return showsLayer('mapillary') || showsLayer('openstreetcam') || showsLayer('streetside');
};
photos.shouldFilterByPhotoType = function() {
return showsLayer('mapillary') ||
(showsLayer('streetside') && showsLayer('openstreetcam'));
};
photos.shouldFilterByUsername = function() {
return !showsLayer('mapillary') && showsLayer('openstreetcam') && !showsLayer('streetside');
};
photos.showsPhotoType = function(val) {
if (!photos.shouldFilterByPhotoType()) return true;
return _shownPhotoTypes.indexOf(val) !== -1;
};
photos.showsFlat = function() {
return photos.showsPhotoType('flat');
};
photos.showsPanoramic = function() {
return photos.showsPhotoType('panoramic');
};
photos.fromDate = function() {
return _fromDate;
};
photos.toDate = function() {
return _toDate;
};
photos.togglePhotoType = function(val) {
var index = _shownPhotoTypes.indexOf(val);
if (index !== -1) {
_shownPhotoTypes.splice(index, 1);
} else {
_shownPhotoTypes.push(val);
}
dispatch.call('change', this);
return photos;
};
photos.usernames = function() {
return _usernames;
};
photos.init = function() {
var hash = utilStringQs(window.location.hash);
if (hash.photo_dates) {
// expect format like `photo_dates=2019-01-01_2020-12-31`, but allow a couple different separators
var parts = /^(.*)[–_](.*)$/g.exec(hash.photo_dates.trim());
this.setDateFilter('fromDate', parts && parts.length >= 2 && parts[1], false);
this.setDateFilter('toDate', parts && parts.length >= 3 && parts[2], false);
}
if (hash.photo_username) {
this.setUsernameFilter(hash.photo_username, false);
}
if (hash.photo_overlay) {
// support enabling photo layers by default via a URL parameter, e.g. `photo_overlay=openstreetcam;mapillary;streetside`
var hashOverlayIDs = hash.photo_overlay.replace(/;/g, ',').split(',');
hashOverlayIDs.forEach(function(id) {
var layer = _layerIDs.indexOf(id) !== -1 && context.layers().layer(id);
if (layer && !layer.enabled()) layer.enabled(true);
});
}
if (hash.photo) {
// support opening a photo via a URL parameter, e.g. `photo=mapillary-fztgSDtLpa08ohPZFZjeRQ`
var photoIds = hash.photo.replace(/;/g, ',').split(',');
var photoId = photoIds.length && photoIds[0].trim();
var results = /(.*)\/(.*)/g.exec(photoId);
if (results && results.length >= 3) {
var serviceId = results[1];
var photoKey = results[2];
var service = services[serviceId];
if (service && service.ensureViewerLoaded) {
// if we're showing a photo then make sure its layer is enabled too
var layer = _layerIDs.indexOf(serviceId) !== -1 && context.layers().layer(serviceId);
if (layer && !layer.enabled()) layer.enabled(true);
var baselineTime = Date.now();
service.on('loadedImages.rendererPhotos', function() {
// don't open the viewer if too much time has elapsed
if (Date.now() - baselineTime > 45000) {
service.on('loadedImages.rendererPhotos', null);
return;
}
if (!service.cachedImage(photoKey)) return;
service.on('loadedImages.rendererPhotos', null);
service.ensureViewerLoaded(context)
.then(function() {
service
.selectImage(context, photoKey)
.showViewer(context);
});
});
}
}
}
context.layers().on('change.rendererPhotos', updateStorage);
};
return utilRebind(photos, dispatch, 'on');
}