import { dispatch as d3_dispatch } from 'd3-dispatch'; import { services } from '../services'; import { utilRebind } from '../util/rebind'; import { utilQsString, utilStringQs } from '../util'; export function rendererPhotos(context) { var dispatch = d3_dispatch('change'); var _layerIDs = ['streetside', 'mapillary', 'mapillary-map-features', 'mapillary-signs', 'kartaview', 'mapilio', 'vegbilder', 'panoramax']; var _allPhotoTypes = ['flat', 'panoramic']; var _shownPhotoTypes = _allPhotoTypes.slice(); // shallow copy var _dateFilters = ['fromDate', 'toDate']; var _fromDate; var _toDate; var _usernames; function photos() {} function updateStorage() { 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.history.replaceState(null, '', '#' + utilQsString(hash, true)); } /** * @returns The layer ID */ photos.overlayLayerIDs = function() { return _layerIDs; }; /** * @returns All the photo types */ photos.allPhotoTypes = function() { return _allPhotoTypes; }; /** * @returns The date filters value */ photos.dateFilters = function() { return _dateFilters; }; photos.dateFilterValue = function(val) { return val === _dateFilters[0] ? _fromDate : _toDate; }; /** * Sets the date filter (min/max date) * @param {*} type Either 'fromDate' or 'toDate' * @param {*} val The actual Date * @param {boolean} updateUrl Whether the URL should update or not */ photos.setDateFilter = function(type, val, updateUrl) { // validate the date var date = val && new Date(val); if (date && !isNaN(date)) { val = date.toISOString().slice(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); } }; /** * Sets the username filter * @param {string} val The username * @param {boolean} updateUrl Whether the URL should update or not */ 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); } }; /** * Util function to set the slider date filter * @param {*} val Either 'panoramic' or 'flat' * @param {boolean} updateUrl Whether the URL should update or not */ photos.togglePhotoType = function(val, updateUrl) { var index = _shownPhotoTypes.indexOf(val); if (index !== -1) { _shownPhotoTypes.splice(index, 1); } else { _shownPhotoTypes.push(val); } if (updateUrl) { var hashString; if (_shownPhotoTypes) { hashString = _shownPhotoTypes.join(','); } setUrlFilterValue('photo_type', hashString); } dispatch.call('change', this); return photos; }; /** * Updates the URL with new values * @param {*} val value to save * @param {string} property Name of the value */ function setUrlFilterValue(property, val) { const 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.history.replaceState(null, '', '#' + utilQsString(hash, true)); } function showsLayer(id) { var layer = context.layers().layer(id); return layer && layer.supported() && layer.enabled(); } /** * @returns If the Date Slider filter should be drawn */ photos.shouldFilterDateBySlider = function(){ return showsLayer('mapillary') || showsLayer('kartaview') || showsLayer('mapilio') || showsLayer('streetside') || showsLayer('vegbilder') || showsLayer('panoramax'); }; /** * @returns If the Photo Type filter should be drawn */ photos.shouldFilterByPhotoType = function() { return showsLayer('mapillary') || (showsLayer('streetside') && showsLayer('kartaview')) || showsLayer('vegbilder') || showsLayer('panoramax'); }; /** * @returns If the Username filter should be drawn */ photos.shouldFilterByUsername = function() { return !showsLayer('mapillary') && showsLayer('kartaview') && !showsLayer('streetside') || showsLayer('panoramax'); }; 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.usernames = function() { return _usernames; }; /** * Inits the streetlevel layer given the saved values in the URL */ photos.init = function() { var hash = utilStringQs(window.location.hash); var parts; if (hash.photo_dates) { // expect format like `photo_dates=2019-01-01_2020-12-31`, but allow a couple different separators 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_type) { parts = hash.photo_type.replace(/;/g, ',').split(','); _allPhotoTypes.forEach(d => { if (!parts.includes(d)) this.togglePhotoType(d, false); }); } if (hash.photo_overlay) { // support enabling photo layers by default via a URL parameter, e.g. `photo_overlay=kartaview;mapillary;streetside` var hashOverlayIDs = hash.photo_overlay.replace(/;/g, ',').split(','); hashOverlayIDs.forEach(function(id) { if (id === 'openstreetcam') id = 'kartaview'; // legacy alias 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]; if (serviceId === 'openstreetcam') serviceId = 'kartaview'; // legacy alias 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'); }