From 6ec363d6182e749ff651b86020907f13c89d3700 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Mon, 27 May 2024 17:17:20 +0200 Subject: [PATCH 01/44] added panoramax css --- css/60_photos.css | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/css/60_photos.css b/css/60_photos.css index 8bf5e3209..5c1708890 100644 --- a/css/60_photos.css +++ b/css/60_photos.css @@ -304,6 +304,51 @@ overflow: hidden } +/* panoramax Image Layer */ +.layer-panoramax { + pointer-events: none; +} +.layer-panoramax .viewfield-group * { + fill: purple; + stroke: #ffffff; + stroke-opacity: .6; + fill-opacity: .6; +} +.layer-panoramax .sequence { + stroke: purple; +} +.photo-controls-panoramax { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; +} +.photo-controls-panoramax button { + padding:0 6px; + pointer-events: initial; +} +.ideditor .panoramax-wrapper { + position: relative; + background-color: #000; + background-image: url(img/loader-black.gif); + background-position: center; + background-repeat: no-repeat; +} +#ideditor-viewer-panoramax-simple-wrap { + height: 100%; +} +#ideditor-viewer-panoramax-simple { + width: 100%; + height: 100%; + transform-origin: 0 0; +} +#ideditor-viewer-panoramax-simple img { + width: 100%; + height: 100%; + object-fit: cover; + overflow: hidden +} + /* Streetside Viewer (pannellum) */ .ms-wrapper .photo-attribution .image-link { From ab3158e48a60a39e7ff4c688eaf149733b5d4d20 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Mon, 27 May 2024 17:50:35 +0200 Subject: [PATCH 02/44] added mock Panoramax (mapilio clone) to photo layer list --- data/core.yaml | 3 + modules/renderer/background.js | 4 +- modules/renderer/photos.js | 2 +- modules/services/index.js | 7 +- modules/services/panoramax.js | 620 ++++++++++++++++++++++++++++++++ modules/svg/layers.js | 2 + modules/svg/panoramax_images.js | 253 +++++++++++++ 7 files changed, 887 insertions(+), 4 deletions(-) create mode 100644 modules/services/panoramax.js create mode 100644 modules/svg/panoramax_images.js diff --git a/data/core.yaml b/data/core.yaml index 5a13caeb3..f0ed90d24 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -1449,6 +1449,9 @@ en: mapilio: title: Mapilio tooltip: "Street-level photos from Mapilio" + panoramax: + title: Panoramax + tooltip: "Street-level photos from Panoramax" street_side: minzoom_tooltip: "Zoom in to see street-side photos" local_photos: diff --git a/modules/renderer/background.js b/modules/renderer/background.js index 90d69d97f..566a6c25d 100644 --- a/modules/renderer/background.js +++ b/modules/renderer/background.js @@ -13,6 +13,7 @@ import { rendererBackgroundSource } from './background_source'; import { rendererTileLayer } from './tile_layer'; import { utilQsString, utilStringQs } from '../util'; import { utilRebind } from '../util/rebind'; +import panoramax from '../services/panoramax'; let _imageryIndex = null; @@ -253,7 +254,8 @@ export function rendererBackground(context) { 'mapillary-signs': 'Mapillary Signs', kartaview: 'KartaView Images', vegbilder: 'Norwegian Road Administration Images', - mapilio: 'Mapilio Images' + mapilio: 'Mapilio Images', + panoramax: 'Panoramax Images' }; for (let layerID in photoOverlayLayers) { diff --git a/modules/renderer/photos.js b/modules/renderer/photos.js index 7c3fa952a..4050ff42c 100644 --- a/modules/renderer/photos.js +++ b/modules/renderer/photos.js @@ -7,7 +7,7 @@ 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']; + 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']; diff --git a/modules/services/index.js b/modules/services/index.js index 896fcc127..81fc9036e 100644 --- a/modules/services/index.js +++ b/modules/services/index.js @@ -15,6 +15,7 @@ import serviceVectorTile from './vector_tile'; import serviceWikidata from './wikidata'; import serviceWikipedia from './wikipedia'; import serviceMapilio from './mapilio'; +import servicePanoramax from './panoramax'; export let services = { @@ -34,7 +35,8 @@ export let services = { vectorTile: serviceVectorTile, wikidata: serviceWikidata, wikipedia: serviceWikipedia, - mapilio: serviceMapilio + mapilio: serviceMapilio, + panoramax: servicePanoramax }; export { @@ -54,5 +56,6 @@ export { serviceVectorTile, serviceWikidata, serviceWikipedia, - serviceMapilio + serviceMapilio, + servicePanoramax }; diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js new file mode 100644 index 000000000..ab63c3529 --- /dev/null +++ b/modules/services/panoramax.js @@ -0,0 +1,620 @@ +import { dispatch as d3_dispatch } from 'd3-dispatch'; +import { select as d3_select } from 'd3-selection'; +import { zoom as d3_zoom, zoomIdentity as d3_zoomIdentity } from 'd3-zoom'; + +import Protobuf from 'pbf'; +import RBush from 'rbush'; +import { VectorTile } from '@mapbox/vector-tile'; + +import { utilRebind, utilTiler, utilQsString, utilStringQs, utilSetTransform } from '../util'; +import {geoExtent, geoScaleToZoom} from '../geo'; +import {localizer} from '../core/localizer'; + +const apiUrl = 'https://end.mapilio.com'; +const imageBaseUrl = 'https://cdn.mapilio.com/im'; +const baseTileUrl = 'https://geo.mapilio.com/geoserver/gwc/service/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&LAYER=mapilio:'; +const pointLayer = 'map_points'; +const lineLayer = 'map_roads_line'; +const tileStyle = '&STYLE=&TILEMATRIX=EPSG:900913:{z}&TILEMATRIXSET=EPSG:900913&FORMAT=application/vnd.mapbox-vector-tile&TILECOL={x}&TILEROW={y}'; + +const minZoom = 14; +const dispatch = d3_dispatch('loadedImages', 'loadedLines'); +const imgZoom = d3_zoom() + .extent([[0, 0], [320, 240]]) + .translateExtent([[0, 0], [320, 240]]) + .scaleExtent([1, 15]); +const pannellumViewerCSS = 'pannellum/pannellum.css'; +const pannellumViewerJS = 'pannellum/pannellum.js'; +const resolution = 1080; + +let _activeImage; +let _cache; +let _loadViewerPromise; +let _pannellumViewer; +let _sceneOptions = { + showFullscreenCtrl: false, + autoLoad: true, + yaw: 0, + minHfov: 10, + maxHfov: 90, + hfov: 60, +}; +let _currScene = 0; + + +// Partition viewport into higher zoom tiles +function partitionViewport(projection) { + const z = geoScaleToZoom(projection.scale()); + const z2 = (Math.ceil(z * 2) / 2) + 2.5; // round to next 0.5 and add 2.5 + const tiler = utilTiler().zoomExtent([z2, z2]); + + return tiler.getTiles(projection) + .map(function(tile) { return tile.extent; }); +} + + +// Return no more than `limit` results per partition. +function searchLimited(limit, projection, rtree) { + limit = limit || 5; + + return partitionViewport(projection) + .reduce(function(result, extent) { + const found = rtree.search(extent.bbox()) + .slice(0, limit) + .map(function(d) { return d.data; }); + + return (found.length ? result.concat(found) : result); + }, []); +} + +// Load all data for the specified type from Mapilio vector tiles +function loadTiles(which, url, maxZoom, projection) { + const tiler = utilTiler().zoomExtent([minZoom, maxZoom]).skipNullIsland(true); + const tiles = tiler.getTiles(projection); + + tiles.forEach(function(tile) { + loadTile(which, url, tile); + }); +} + + +// Load all data for the specified type from one vector tile +function loadTile(which, url, tile) { + const cache = _cache.requests; + const tileId = `${tile.id}-${which}`; + if (cache.loaded[tileId] || cache.inflight[tileId]) return; + const controller = new AbortController(); + cache.inflight[tileId] = controller; + const requestUrl = url.replace('{x}', tile.xyz[0]) + .replace('{y}', tile.xyz[1]) + .replace('{z}', tile.xyz[2]); + + fetch(requestUrl, { signal: controller.signal }) + .then(function(response) { + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } + cache.loaded[tileId] = true; + delete cache.inflight[tileId]; + return response.arrayBuffer(); + }) + .then(function(data) { + if (data.byteLength === 0) { + throw new Error('No Data'); + } + + loadTileDataToCache(data, tile, which); + + if (which === 'images') { + dispatch.call('loadedImages'); + } else { + dispatch.call('loadedLines'); + } + }) + .catch(function (e) { + if (e.message === 'No Data') { + cache.loaded[tileId] = true; + } else { + console.error(e); // eslint-disable-line no-console + } + }); +} + + +// Load the data from the vector tile into cache +function loadTileDataToCache(data, tile) { + const vectorTile = new VectorTile(new Protobuf(data)); + let features, + cache, + layer, + i, + feature, + loc, + d; + if (vectorTile.layers.hasOwnProperty(pointLayer)) { + features = []; + cache = _cache.images; + layer = vectorTile.layers[pointLayer]; + + for (i = 0; i < layer.length; i++) { + feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]); + loc = feature.geometry.coordinates; + + let resolutionArr = feature.properties.resolution.split('x'); + let sourceWidth = Math.max(resolutionArr[0], resolutionArr[1]); + let sourceHeight = Math.min(resolutionArr[0] ,resolutionArr[1]); + let isPano = sourceWidth % sourceHeight === 0; + + d = { + loc: loc, + capture_time: feature.properties.capture_time, + id: feature.properties.id, + sequence_id: feature.properties.sequence_uuid, + heading: feature.properties.heading, + resolution: feature.properties.resolution, + isPano: isPano + }; + cache.forImageId[d.id] = d; + features.push({ + minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d + }); + } + if (cache.rtree) { + cache.rtree.load(features); + } + } + + if (vectorTile.layers.hasOwnProperty(lineLayer)) { + cache = _cache.sequences; + layer = vectorTile.layers[lineLayer]; + + for (i = 0; i < layer.length; i++) { + feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]); + if (cache.lineString[feature.properties.sequence_uuid]) { + cache.lineString[feature.properties.sequence_uuid].push(feature); + } else { + cache.lineString[feature.properties.sequence_uuid] = [feature]; + } + } + } + +} + +function getImageData(imageId, sequenceId) { + + return fetch(apiUrl + `/api/sequence-detail?sequence_uuid=${sequenceId}`, {method: 'GET'}) + .then(function (response) { + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } + return response.json(); + }) + .then(function (data) { + let index = data.data.findIndex((feature) => feature.id === imageId); + const {filename, uploaded_hash} = data.data[index]; + _sceneOptions.panorama = imageBaseUrl + '/' + uploaded_hash + '/' + filename + '/' + resolution; + }); +} + + +export default { + // Initialize Mapilio + init: function() { + if (!_cache) { + this.reset(); + } + + this.event = utilRebind(this, dispatch, 'on'); + }, + + // Reset cache and state + reset: function() { + if (_cache) { + Object.values(_cache.requests.inflight).forEach(function(request) { request.abort(); }); + } + + _cache = { + images: { rtree: new RBush(), forImageId: {} }, + sequences: { rtree: new RBush(), lineString: {} }, + requests: { loaded: {}, inflight: {} } + }; + + _activeImage = null; + }, + + // Get visible images + images: function(projection) { + const limit = 5; + return searchLimited(limit, projection, _cache.images.rtree); + }, + + cachedImage: function(imageKey) { + return _cache.images.forImageId[imageKey]; + }, + + + // Load images in the visible area + loadImages: function(projection) { + let url = baseTileUrl + pointLayer + tileStyle; + loadTiles('images', url, 14, projection); + }, + + // Load line in the visible area + loadLines: function(projection) { + let url = baseTileUrl + lineLayer + tileStyle; + loadTiles('line', url, 14, projection); + }, + + // Get visible sequences + sequences: function(projection) { + const viewport = projection.clipExtent(); + const min = [viewport[0][0], viewport[1][1]]; + const max = [viewport[1][0], viewport[0][1]]; + const bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox(); + const sequenceIds = {}; + let lineStrings = []; + + _cache.images.rtree.search(bbox) + .forEach(function(d) { + if (d.data.sequence_id) { + sequenceIds[d.data.sequence_id] = true; + } + }); + + Object.keys(sequenceIds).forEach(function(sequenceId) { + if (_cache.sequences.lineString[sequenceId]) { + lineStrings = lineStrings.concat(_cache.sequences.lineString[sequenceId]); + } + }); + + return lineStrings; + }, + + // Set the currently visible image + setActiveImage: function(image) { + if (image) { + _activeImage = { + id: image.id, + sequence_id: image.sequence_id + }; + } else { + _activeImage = null; + } + }, + + + // Update the currently highlighted sequence and selected bubble. + setStyles: function(context, hovered) { + const hoveredImageId = hovered && hovered.id; + const hoveredSequenceId = hovered && hovered.sequence_id; + const selectedSequenceId = _activeImage && _activeImage.sequence_id; + const selectedImageId = _activeImage && _activeImage.id; + + const markers = context.container().selectAll('.layer-mapilio .viewfield-group'); + const sequences = context.container().selectAll('.layer-mapilio .sequence'); + + markers.classed('highlighted', function(d) { return d.id === hoveredImageId; }) + .classed('hovered', function(d) { return d.id === hoveredImageId; }) + .classed('currentView', function(d) { return d.id === selectedImageId; }); + + sequences.classed('highlighted', function(d) { return d.properties.sequence_uuid === hoveredSequenceId; }) + .classed('currentView', function(d) { return d.properties.sequence_uuid === selectedSequenceId; }); + + return this; + }, + + updateUrlImage: function(imageKey) { + if (!window.mocha) { + var hash = utilStringQs(window.location.hash); + if (imageKey) { + hash.photo = 'mapilio/' + imageKey; + } else { + delete hash.photo; + } + window.location.replace('#' + utilQsString(hash, true)); + } + }, + + initViewer: function () { + if (!window.pannellum) return; + if (_pannellumViewer) return; + + _currScene += 1; + const sceneID = _currScene.toString(); + const options = { + 'default': { firstScene: sceneID }, + scenes: {} + }; + options.scenes[sceneID] = _sceneOptions; + + _pannellumViewer = window.pannellum.viewer('ideditor-viewer-mapilio-pnlm', options); + }, + + selectImage: function (context, id) { + + let that = this; + + let d = this.cachedImage(id); + + this.setActiveImage(d); + + this.updateUrlImage(d.id); + + let viewer = context.container().select('.photoviewer'); + if (!viewer.empty()) viewer.datum(d); + + this.setStyles(context, null); + + if (!d) return this; + + let wrap = context.container().select('.photoviewer .mapilio-wrapper'); + let attribution = wrap.selectAll('.photo-attribution').text(''); + + if (d.capture_time) { + attribution + .append('span') + .attr('class', 'captured_at') + .text(localeDateString(d.capture_time)); + + attribution + .append('span') + .text('|'); + } + + attribution + .append('a') + .attr('class', 'image-link') + .attr('target', '_blank') + .attr('href', `https://mapilio.com/app?lat=${d.loc[1]}&lng=${d.loc[0]}&zoom=17&pId=${d.id}`) + .text('mapilio.com'); + + wrap + .transition() + .duration(100) + .call(imgZoom.transform, d3_zoomIdentity); + + wrap + .selectAll('img') + .remove(); + + wrap + .selectAll('button.back') + .classed('hide', !_cache.images.forImageId.hasOwnProperty(+id - 1)); + wrap + .selectAll('button.forward') + .classed('hide', !_cache.images.forImageId.hasOwnProperty(+id + 1)); + + + getImageData(d.id,d.sequence_id).then(function () { + + if (d.isPano) { + if (!_pannellumViewer) { + that.initViewer(); + } else { + // make a new scene + _currScene += 1; + let sceneID = _currScene.toString(); + _pannellumViewer + .addScene(sceneID, _sceneOptions) + .loadScene(sceneID); + + // remove previous scene + if (_currScene > 2) { + sceneID = (_currScene - 1).toString(); + _pannellumViewer + .removeScene(sceneID); + } + } + } else { + // make non-panoramic photo viewer + that.initOnlyPhoto(context); + } + }); + + function localeDateString(s) { + if (!s) return null; + var options = { day: 'numeric', month: 'short', year: 'numeric' }; + var d = new Date(s); + if (isNaN(d.getTime())) return null; + return d.toLocaleDateString(localizer.localeCode(), options); + } + + return this; + }, + + initOnlyPhoto: function (context) { + + if (_pannellumViewer) { + _pannellumViewer.destroy(); + _pannellumViewer = null; + } + + let wrap = context.container().select('#ideditor-viewer-mapilio-simple'); + + let imgWrap = wrap.select('img'); + + if (!imgWrap.empty()) { + imgWrap.attr('src',_sceneOptions.panorama); + } else { + wrap.append('img') + .attr('src',_sceneOptions.panorama); + } + + }, + + ensureViewerLoaded: function(context) { + + let that = this; + + let imgWrap = context.container().select('#ideditor-viewer-mapilio-simple > img'); + + if (!imgWrap.empty()) { + imgWrap.remove(); + } + + if (_loadViewerPromise) return _loadViewerPromise; + + let wrap = context.container().select('.photoviewer').selectAll('.mapilio-wrapper') + .data([0]); + + let wrapEnter = wrap.enter() + .append('div') + .attr('class', 'photo-wrapper mapilio-wrapper') + .classed('hide', true) + .on('dblclick.zoom', null); + + wrapEnter + .append('div') + .attr('class', 'photo-attribution fillD'); + + const controlsEnter = wrapEnter + .append('div') + .attr('class', 'photo-controls-wrap') + .append('div') + .attr('class', 'photo-controls-mapilio'); + + controlsEnter + .append('button') + .classed('back', true) + .on('click.back', step(-1)) + .text('◄'); + + controlsEnter + .append('button') + .classed('forward', true) + .on('click.forward', step(1)) + .text('►'); + + wrapEnter + .append('div') + .attr('id', 'ideditor-viewer-mapilio-pnlm'); + + wrapEnter + .append('div') + .attr('id', 'ideditor-viewer-mapilio-simple-wrap') + .call(imgZoom.on('zoom', zoomPan)) + .append('div') + .attr('id', 'ideditor-viewer-mapilio-simple'); + + + + // Register viewer resize handler + context.ui().photoviewer.on('resize.mapilio', () => { + if (_pannellumViewer) { + _pannellumViewer.resize(); + } + }); + + _loadViewerPromise = new Promise((resolve, reject) => { + let loadedCount = 0; + function loaded() { + loadedCount += 1; + + // wait until both files are loaded + if (loadedCount === 2) resolve(); + } + + const head = d3_select('head'); + + // load pannellum-viewercss + head.selectAll('#ideditor-mapilio-viewercss') + .data([0]) + .enter() + .append('link') + .attr('id', 'ideditor-mapilio-viewercss') + .attr('rel', 'stylesheet') + .attr('crossorigin', 'anonymous') + .attr('href', context.asset(pannellumViewerCSS)) + .on('load.serviceMapilio', loaded) + .on('error.serviceMapilio', function() { + reject(); + }); + + // load pannellum-viewerjs + head.selectAll('#ideditor-mapilio-viewerjs') + .data([0]) + .enter() + .append('script') + .attr('id', 'ideditor-mapilio-viewerjs') + .attr('crossorigin', 'anonymous') + .attr('src', context.asset(pannellumViewerJS)) + .on('load.serviceMapilio', loaded) + .on('error.serviceMapilio', function() { + reject(); + }); + }) + .catch(function() { + _loadViewerPromise = null; + }); + + function step(stepBy) { + return function () { + if (!_activeImage) return; + const imageId = _activeImage.id; + + const nextIndex = imageId + stepBy; + if (!nextIndex) return; + + const nextImage = _cache.images.forImageId[nextIndex]; + + context.map().centerEase(nextImage.loc); + + that.selectImage(context, nextImage.id); + }; + } + + function zoomPan(d3_event) { + var t = d3_event.transform; + context.container().select('.photoviewer #ideditor-viewer-mapilio-simple') + .call(utilSetTransform, t.x, t.y, t.k); + } + + return _loadViewerPromise; + }, + + showViewer:function (context) { + let wrap = context.container().select('.photoviewer') + .classed('hide', false); + + let isHidden = wrap.selectAll('.photo-wrapper.mapilio-wrapper.hide').size(); + + if (isHidden) { + wrap + .selectAll('.photo-wrapper:not(.mapilio-wrapper)') + .classed('hide', true); + + wrap + .selectAll('.photo-wrapper.mapilio-wrapper') + .classed('hide', false); + } + + return this; + }, + + /** + * hideViewer() + */ + hideViewer: function (context) { + let viewer = context.container().select('.photoviewer'); + if (!viewer.empty()) viewer.datum(null); + + this.updateUrlImage(null); + + viewer + .classed('hide', true) + .selectAll('.photo-wrapper') + .classed('hide', true); + + context.container().selectAll('.viewfield-group, .sequence, .icon-sign') + .classed('currentView', false); + + this.setActiveImage(); + + return this.setStyles(context, null); + }, + + // Return the current cache + cache: function() { + return _cache; + } +}; diff --git a/modules/svg/layers.js b/modules/svg/layers.js index b213334d4..4db0de629 100644 --- a/modules/svg/layers.js +++ b/modules/svg/layers.js @@ -16,6 +16,7 @@ import { svgMapillarySigns } from './mapillary_signs'; import { svgMapillaryMapFeatures } from './mapillary_map_features'; import { svgKartaviewImages } from './kartaview_images'; import { svgMapilioImages } from './mapilio_images'; +import { svgPanoramaxImages } from './panoramax_images'; import { svgOsm } from './osm'; import { svgNotes } from './notes'; import { svgTouch } from './touch'; @@ -40,6 +41,7 @@ export function svgLayers(projection, context) { { id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) }, { id: 'kartaview', layer: svgKartaviewImages(projection, context, dispatch) }, { id: 'mapilio', layer: svgMapilioImages(projection, context, dispatch) }, + { id: 'panoramax', layer: svgPanoramaxImages(projection, context, dispatch) }, { id: 'vegbilder', layer: svgVegbilder(projection, context, dispatch) }, { id: 'local-photos', layer: svgLocalPhotos(projection, context, dispatch) }, { id: 'debug', layer: svgDebug(projection, context, dispatch) }, diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js new file mode 100644 index 000000000..7faeeca66 --- /dev/null +++ b/modules/svg/panoramax_images.js @@ -0,0 +1,253 @@ +import _throttle from 'lodash-es/throttle'; + +import { select as d3_select } from 'd3-selection'; +import { services } from '../services'; +import {svgPath, svgPointTransform} from './helpers'; + + +export function svgPanoramaxImages(projection, context, dispatch) { + const throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); + const minZoom = 12; + let layer = d3_select(null); + let _panoramax; + const viewFieldZoomLevel = 18; + + + function init() { + if (svgPanoramaxImages.initialized) return; + svgPanoramaxImages.enabled = false; + svgPanoramaxImages.initialized = true; + } + + + function getService() { + if (services.panoramax && !_panoramax) { + _panoramax = services.panoramax; + _panoramax.event.on('loadedImages', throttledRedraw); + } else if (!services.panoramax && _panoramax) { + _panoramax = null; + } + + return _panoramax; + } + + + function showLayer() { + const service = getService(); + if (!service) return; + + editOn(); + + layer + .style('opacity', 0) + .transition() + .duration(250) + .style('opacity', 1) + .on('end', function () { dispatch.call('change'); }); + } + + + function hideLayer() { + throttledRedraw.cancel(); + + layer + .transition() + .duration(250) + .style('opacity', 0) + .on('end', editOff); + } + + function transform(d) { + let t = svgPointTransform(projection)(d); + if (d.heading) { + t += ' rotate(' + Math.floor(d.heading) + ',0,0)'; + } + return t; + } + + + function editOn() { + layer.style('display', 'block'); + } + + + function editOff() { + layer.selectAll('.viewfield-group').remove(); + layer.style('display', 'none'); + } + + function click(d3_event, image) { + const service = getService(); + if (!service) return; + + service + .ensureViewerLoaded(context, image.id) + .then(function() { + service + .selectImage(context, image.id) + .showViewer(context); + }); + + context.map().centerEase(image.loc); + } + + function mouseover(d3_event, image) { + const service = getService(); + if (service) service.setStyles(context, image); + } + + + function mouseout() { + const service = getService(); + if (service) service.setStyles(context, null); + } + + function update() { + + const z = ~~context.map().zoom(); + const showViewfields = (z >= viewFieldZoomLevel); + + const service = getService(); + let sequences = (service ? service.sequences(projection) : []); + let images = (service ? service.images(projection) : []); + + let traces = layer.selectAll('.sequences').selectAll('.sequence') + .data(sequences, function(d) { return d.properties.id; }); + + // exit + traces.exit() + .remove(); + + traces.enter() + .append('path') + .attr('class', 'sequence') + .merge(traces) + .attr('d', svgPath(projection).geojson); + + + const groups = layer.selectAll('.markers').selectAll('.viewfield-group') + .data(images, function(d) { return d.id; }); + + // exit + groups.exit() + .remove(); + + // enter + const groupsEnter = groups.enter() + .append('g') + .attr('class', 'viewfield-group') + .on('mouseenter', mouseover) + .on('mouseleave', mouseout) + .on('click', click); + + groupsEnter + .append('g') + .attr('class', 'viewfield-scale'); + + // update + const markers = groups + .merge(groupsEnter) + .sort(function(a, b) { + return b.loc[1] - a.loc[1]; // sort Y + }) + .attr('transform', transform) + .select('.viewfield-scale'); + + + markers.selectAll('circle') + .data([0]) + .enter() + .append('circle') + .attr('dx', '0') + .attr('dy', '0') + .attr('r', '6'); + + const viewfields = markers.selectAll('.viewfield') + .data(showViewfields ? [0] : []); + + viewfields.exit() + .remove(); + + viewfields.enter() + .insert('path', 'circle') + .attr('class', 'viewfield') + .attr('transform', 'scale(1.5,1.5),translate(-8, -13)') + .attr('d', viewfieldPath); + + function viewfieldPath() { + if (this.parentNode.__data__.isPano) { + return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; + } else { + return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z'; + } + } + + } + + + function drawImages(selection) { + const enabled = svgPanoramaxImages.enabled; + const service = getService(); + + layer = selection.selectAll('.layer-panoramax') + .data(service ? [0] : []); + + layer.exit() + .remove(); + + const layerEnter = layer.enter() + .append('g') + .attr('class', 'layer-panoramax') + .style('display', enabled ? 'block' : 'none'); + + layerEnter + .append('g') + .attr('class', 'sequences'); + + layerEnter + .append('g') + .attr('class', 'markers'); + + layer = layerEnter + .merge(layer); + + if (enabled) { + if (service && ~~context.map().zoom() >= minZoom) { + editOn(); + update(); + service.loadImages(projection); + service.loadLines(projection); + } else { + editOff(); + } + } + } + + + drawImages.enabled = function(_) { + if (!arguments.length) return svgPanoramaxImages.enabled; + svgPanoramaxImages.enabled = _; + if (svgPanoramaxImages.enabled) { + showLayer(); + context.photos().on('change.panoramax_images', null); + } else { + hideLayer(); + context.photos().on('change.panoramax_images', null); + } + dispatch.call('change'); + return this; + }; + + + drawImages.supported = function() { + return !!getService(); + }; + + drawImages.rendered = function(zoom) { + return zoom >= minZoom; + }; + + + init(); + return drawImages; +} From 0ac62ba2b61942b06cb1057199cb3684d125d0ea Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Wed, 29 May 2024 10:55:15 +0200 Subject: [PATCH 03/44] playing with the panormax API --- modules/services/panoramax.js | 74 +++++++++++++++++------------------ 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index ab63c3529..de06ff20f 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -7,15 +7,15 @@ import RBush from 'rbush'; import { VectorTile } from '@mapbox/vector-tile'; import { utilRebind, utilTiler, utilQsString, utilStringQs, utilSetTransform } from '../util'; -import {geoExtent, geoScaleToZoom} from '../geo'; -import {localizer} from '../core/localizer'; +import { geoExtent, geoScaleToZoom } from '../geo'; +import { localizer } from '../core/localizer'; + +const apiUrl = 'https://panoramax.openstreetmap.fr/api'; +const tileUrl = apiUrl + '/map/{z}/{x}/{y}.pbf'; -const apiUrl = 'https://end.mapilio.com'; -const imageBaseUrl = 'https://cdn.mapilio.com/im'; -const baseTileUrl = 'https://geo.mapilio.com/geoserver/gwc/service/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&LAYER=mapilio:'; const pointLayer = 'map_points'; const lineLayer = 'map_roads_line'; -const tileStyle = '&STYLE=&TILEMATRIX=EPSG:900913:{z}&TILEMATRIXSET=EPSG:900913&FORMAT=application/vnd.mapbox-vector-tile&TILECOL={x}&TILEROW={y}'; + const minZoom = 14; const dispatch = d3_dispatch('loadedImages', 'loadedLines'); @@ -67,7 +67,7 @@ function searchLimited(limit, projection, rtree) { }, []); } -// Load all data for the specified type from Mapilio vector tiles +// Load all data for the specified type from Panoramax vector tiles function loadTiles(which, url, maxZoom, projection) { const tiler = utilTiler().zoomExtent([minZoom, maxZoom]).skipNullIsland(true); const tiles = tiler.getTiles(projection); @@ -182,7 +182,7 @@ function loadTileDataToCache(data, tile) { function getImageData(imageId, sequenceId) { - return fetch(apiUrl + `/api/sequence-detail?sequence_uuid=${sequenceId}`, {method: 'GET'}) + return fetch(apiUrl + `/api/collections/${sequenceId}`, {method: 'GET'}) .then(function (response) { if (!response.ok) { throw new Error(response.status + ' ' + response.statusText); @@ -198,7 +198,7 @@ function getImageData(imageId, sequenceId) { export default { - // Initialize Mapilio + // Initialize Panoramax init: function() { if (!_cache) { this.reset(); @@ -235,13 +235,13 @@ export default { // Load images in the visible area loadImages: function(projection) { - let url = baseTileUrl + pointLayer + tileStyle; + let url = tileUrl; loadTiles('images', url, 14, projection); }, // Load line in the visible area loadLines: function(projection) { - let url = baseTileUrl + lineLayer + tileStyle; + let url = tileUrl; loadTiles('line', url, 14, projection); }, @@ -290,8 +290,8 @@ export default { const selectedSequenceId = _activeImage && _activeImage.sequence_id; const selectedImageId = _activeImage && _activeImage.id; - const markers = context.container().selectAll('.layer-mapilio .viewfield-group'); - const sequences = context.container().selectAll('.layer-mapilio .sequence'); + const markers = context.container().selectAll('.layer-panoramax .viewfield-group'); + const sequences = context.container().selectAll('.layer-panoramax .sequence'); markers.classed('highlighted', function(d) { return d.id === hoveredImageId; }) .classed('hovered', function(d) { return d.id === hoveredImageId; }) @@ -307,7 +307,7 @@ export default { if (!window.mocha) { var hash = utilStringQs(window.location.hash); if (imageKey) { - hash.photo = 'mapilio/' + imageKey; + hash.photo = 'panoramax/' + imageKey; } else { delete hash.photo; } @@ -327,7 +327,7 @@ export default { }; options.scenes[sceneID] = _sceneOptions; - _pannellumViewer = window.pannellum.viewer('ideditor-viewer-mapilio-pnlm', options); + _pannellumViewer = window.pannellum.viewer('ideditor-viewer-panoramax-pnlm', options); }, selectImage: function (context, id) { @@ -347,7 +347,7 @@ export default { if (!d) return this; - let wrap = context.container().select('.photoviewer .mapilio-wrapper'); + let wrap = context.container().select('.photoviewer .panoramax-wrapper'); let attribution = wrap.selectAll('.photo-attribution').text(''); if (d.capture_time) { @@ -365,8 +365,8 @@ export default { .append('a') .attr('class', 'image-link') .attr('target', '_blank') - .attr('href', `https://mapilio.com/app?lat=${d.loc[1]}&lng=${d.loc[0]}&zoom=17&pId=${d.id}`) - .text('mapilio.com'); + .attr('href', apiUrl + '/pictures/' + d.id + '/hd.jpg') + .text('panoramax.com'); wrap .transition() @@ -429,7 +429,7 @@ export default { _pannellumViewer = null; } - let wrap = context.container().select('#ideditor-viewer-mapilio-simple'); + let wrap = context.container().select('#ideditor-viewer-panoramax-simple'); let imgWrap = wrap.select('img'); @@ -446,7 +446,7 @@ export default { let that = this; - let imgWrap = context.container().select('#ideditor-viewer-mapilio-simple > img'); + let imgWrap = context.container().select('#ideditor-viewer-panoramax-simple > img'); if (!imgWrap.empty()) { imgWrap.remove(); @@ -454,12 +454,12 @@ export default { if (_loadViewerPromise) return _loadViewerPromise; - let wrap = context.container().select('.photoviewer').selectAll('.mapilio-wrapper') + let wrap = context.container().select('.photoviewer').selectAll('.panoramax-wrapper') .data([0]); let wrapEnter = wrap.enter() .append('div') - .attr('class', 'photo-wrapper mapilio-wrapper') + .attr('class', 'photo-wrapper panoramax-wrapper') .classed('hide', true) .on('dblclick.zoom', null); @@ -471,7 +471,7 @@ export default { .append('div') .attr('class', 'photo-controls-wrap') .append('div') - .attr('class', 'photo-controls-mapilio'); + .attr('class', 'photo-controls-panoramax'); controlsEnter .append('button') @@ -487,19 +487,19 @@ export default { wrapEnter .append('div') - .attr('id', 'ideditor-viewer-mapilio-pnlm'); + .attr('id', 'ideditor-viewer-panoramax-pnlm'); wrapEnter .append('div') - .attr('id', 'ideditor-viewer-mapilio-simple-wrap') + .attr('id', 'ideditor-viewer-panoramax-simple-wrap') .call(imgZoom.on('zoom', zoomPan)) .append('div') - .attr('id', 'ideditor-viewer-mapilio-simple'); + .attr('id', 'ideditor-viewer-panoramax-simple'); // Register viewer resize handler - context.ui().photoviewer.on('resize.mapilio', () => { + context.ui().photoviewer.on('resize.panoramax', () => { if (_pannellumViewer) { _pannellumViewer.resize(); } @@ -517,11 +517,11 @@ export default { const head = d3_select('head'); // load pannellum-viewercss - head.selectAll('#ideditor-mapilio-viewercss') + head.selectAll('#ideditor-panoramax-viewercss') .data([0]) .enter() .append('link') - .attr('id', 'ideditor-mapilio-viewercss') + .attr('id', 'ideditor-panoramax-viewercss') .attr('rel', 'stylesheet') .attr('crossorigin', 'anonymous') .attr('href', context.asset(pannellumViewerCSS)) @@ -531,11 +531,11 @@ export default { }); // load pannellum-viewerjs - head.selectAll('#ideditor-mapilio-viewerjs') + head.selectAll('#ideditor-panoramax-viewerjs') .data([0]) .enter() .append('script') - .attr('id', 'ideditor-mapilio-viewerjs') + .attr('id', 'ideditor-panoramax-viewerjs') .attr('crossorigin', 'anonymous') .attr('src', context.asset(pannellumViewerJS)) .on('load.serviceMapilio', loaded) @@ -565,7 +565,7 @@ export default { function zoomPan(d3_event) { var t = d3_event.transform; - context.container().select('.photoviewer #ideditor-viewer-mapilio-simple') + context.container().select('.photoviewer #ideditor-viewer-panoramax-simple') .call(utilSetTransform, t.x, t.y, t.k); } @@ -576,24 +576,21 @@ export default { let wrap = context.container().select('.photoviewer') .classed('hide', false); - let isHidden = wrap.selectAll('.photo-wrapper.mapilio-wrapper.hide').size(); + let isHidden = wrap.selectAll('.photo-wrapper.panoramax-wrapper.hide').size(); if (isHidden) { wrap - .selectAll('.photo-wrapper:not(.mapilio-wrapper)') + .selectAll('.photo-wrapper:not(.panoramax-wrapper)') .classed('hide', true); wrap - .selectAll('.photo-wrapper.mapilio-wrapper') + .selectAll('.photo-wrapper.panoramax-wrapper') .classed('hide', false); } return this; }, - /** - * hideViewer() - */ hideViewer: function (context) { let viewer = context.container().select('.photoviewer'); if (!viewer.empty()) viewer.datum(null); @@ -613,7 +610,6 @@ export default { return this.setStyles(context, null); }, - // Return the current cache cache: function() { return _cache; } From ff2f50857559100bb10d2243fd11679e82534609 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Thu, 30 May 2024 14:59:00 +0200 Subject: [PATCH 04/44] trying stuff with API (again) (some issues uh) --- modules/renderer/background.js | 1 - modules/services/panoramax.js | 64 ++++++++++++++++----------------- modules/svg/index.js | 1 + modules/svg/panoramax_images.js | 1 - modules/ui/photoviewer.js | 1 + 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/modules/renderer/background.js b/modules/renderer/background.js index 566a6c25d..48eddf956 100644 --- a/modules/renderer/background.js +++ b/modules/renderer/background.js @@ -13,7 +13,6 @@ import { rendererBackgroundSource } from './background_source'; import { rendererTileLayer } from './tile_layer'; import { utilQsString, utilStringQs } from '../util'; import { utilRebind } from '../util/rebind'; -import panoramax from '../services/panoramax'; let _imageryIndex = null; diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index de06ff20f..54e2509be 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -10,12 +10,11 @@ import { utilRebind, utilTiler, utilQsString, utilStringQs, utilSetTransform } f import { geoExtent, geoScaleToZoom } from '../geo'; import { localizer } from '../core/localizer'; -const apiUrl = 'https://panoramax.openstreetmap.fr/api'; -const tileUrl = apiUrl + '/map/{z}/{x}/{y}.pbf'; - -const pointLayer = 'map_points'; -const lineLayer = 'map_roads_line'; +const apiUrl = 'https://panoramax.openstreetmap.fr/'; +const tileUrl = apiUrl + 'api/map/{z}/{x}/{y}.pbf'; +const pictureLayer = 'pictures'; +const sequenceLayer = 'sequences'; const minZoom = 14; const dispatch = d3_dispatch('loadedImages', 'loadedLines'); @@ -124,6 +123,9 @@ function loadTile(which, url, tile) { // Load the data from the vector tile into cache function loadTileDataToCache(data, tile) { const vectorTile = new VectorTile(new Protobuf(data)); + + console.log(vectorTile) + let features, cache, layer, @@ -131,28 +133,26 @@ function loadTileDataToCache(data, tile) { feature, loc, d; - if (vectorTile.layers.hasOwnProperty(pointLayer)) { + + if (vectorTile.layers.hasOwnProperty(pictureLayer)) { features = []; cache = _cache.images; - layer = vectorTile.layers[pointLayer]; + layer = vectorTile.layers[pictureLayer]; for (i = 0; i < layer.length; i++) { feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]); loc = feature.geometry.coordinates; - let resolutionArr = feature.properties.resolution.split('x'); - let sourceWidth = Math.max(resolutionArr[0], resolutionArr[1]); - let sourceHeight = Math.min(resolutionArr[0] ,resolutionArr[1]); - let isPano = sourceWidth % sourceHeight === 0; - d = { loc: loc, - capture_time: feature.properties.capture_time, + capture_time: feature.properties.ts, id: feature.properties.id, - sequence_id: feature.properties.sequence_uuid, + acc_id: feature.properties.account_id, + sequence_id: feature.properties.sequences[0], heading: feature.properties.heading, resolution: feature.properties.resolution, - isPano: isPano + type: feature.properties.type, + model: feature.properties.model, }; cache.forImageId[d.id] = d; features.push({ @@ -162,18 +162,20 @@ function loadTileDataToCache(data, tile) { if (cache.rtree) { cache.rtree.load(features); } + console.log(feature) } - if (vectorTile.layers.hasOwnProperty(lineLayer)) { + if (vectorTile.layers.hasOwnProperty(sequenceLayer)) { + features = []; cache = _cache.sequences; - layer = vectorTile.layers[lineLayer]; + layer = vectorTile.layers[sequenceLayer]; for (i = 0; i < layer.length; i++) { feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]); - if (cache.lineString[feature.properties.sequence_uuid]) { - cache.lineString[feature.properties.sequence_uuid].push(feature); + if (cache.lineString[feature.properties.id]) { + cache.lineString[feature.properties.id].push(feature); } else { - cache.lineString[feature.properties.sequence_uuid] = [feature]; + cache.lineString[feature.properties.id] = [feature]; } } } @@ -182,7 +184,7 @@ function loadTileDataToCache(data, tile) { function getImageData(imageId, sequenceId) { - return fetch(apiUrl + `/api/collections/${sequenceId}`, {method: 'GET'}) + return fetch(apiUrl + `api/collections/${sequenceId}/items`, {method: 'GET'}) .then(function (response) { if (!response.ok) { throw new Error(response.status + ' ' + response.statusText); @@ -198,7 +200,6 @@ function getImageData(imageId, sequenceId) { export default { - // Initialize Panoramax init: function() { if (!_cache) { this.reset(); @@ -207,7 +208,6 @@ export default { this.event = utilRebind(this, dispatch, 'on'); }, - // Reset cache and state reset: function() { if (_cache) { Object.values(_cache.requests.inflight).forEach(function(request) { request.abort(); }); @@ -297,8 +297,8 @@ export default { .classed('hovered', function(d) { return d.id === hoveredImageId; }) .classed('currentView', function(d) { return d.id === selectedImageId; }); - sequences.classed('highlighted', function(d) { return d.properties.sequence_uuid === hoveredSequenceId; }) - .classed('currentView', function(d) { return d.properties.sequence_uuid === selectedSequenceId; }); + sequences.classed('highlighted', function(d) { return d.properties.sequence_id === hoveredSequenceId; }) + .classed('currentView', function(d) { return d.properties.sequence_id === selectedSequenceId; }); return this; }, @@ -365,8 +365,8 @@ export default { .append('a') .attr('class', 'image-link') .attr('target', '_blank') - .attr('href', apiUrl + '/pictures/' + d.id + '/hd.jpg') - .text('panoramax.com'); + .attr('href', apiUrl + 'api/pictures/' + d.id + '/hd.jpg') + .text('panoramax.fr'); wrap .transition() @@ -387,7 +387,7 @@ export default { getImageData(d.id,d.sequence_id).then(function () { - if (d.isPano) { + if (d.type == "equirectangular") { if (!_pannellumViewer) { that.initViewer(); } else { @@ -525,8 +525,8 @@ export default { .attr('rel', 'stylesheet') .attr('crossorigin', 'anonymous') .attr('href', context.asset(pannellumViewerCSS)) - .on('load.serviceMapilio', loaded) - .on('error.serviceMapilio', function() { + .on('load.servicePanoramax', loaded) + .on('error.servicePanoramax', function() { reject(); }); @@ -538,8 +538,8 @@ export default { .attr('id', 'ideditor-panoramax-viewerjs') .attr('crossorigin', 'anonymous') .attr('src', context.asset(pannellumViewerJS)) - .on('load.serviceMapilio', loaded) - .on('error.serviceMapilio', function() { + .on('load.servicePanoramax', loaded) + .on('error.servicePanoramax', function() { reject(); }); }) diff --git a/modules/svg/index.js b/modules/svg/index.js index c7fe6ca0c..9caffc5ed 100644 --- a/modules/svg/index.js +++ b/modules/svg/index.js @@ -29,3 +29,4 @@ export { svgTouch } from './touch.js'; export { svgTurns } from './turns.js'; export { svgVertices } from './vertices.js'; export { svgMapilioImages } from './mapilio_images.js'; +export { svgPanoramaxImages } from './panoramax_images.js'; diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 7faeeca66..20d91ccf4 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -216,7 +216,6 @@ export function svgPanoramaxImages(projection, context, dispatch) { editOn(); update(); service.loadImages(projection); - service.loadLines(projection); } else { editOff(); } diff --git a/modules/ui/photoviewer.js b/modules/ui/photoviewer.js index 0d1240ff8..aeb600b07 100644 --- a/modules/ui/photoviewer.js +++ b/modules/ui/photoviewer.js @@ -25,6 +25,7 @@ export function uiPhotoviewer(context) { if (services.mapillary) { services.mapillary.hideViewer(context); } if (services.kartaview) { services.kartaview.hideViewer(context); } if (services.mapilio) { services.mapilio.hideViewer(context); } + if (services.panoramax) { services.panoramax.hideViewer(context); } if (services.vegbilder) { services.vegbilder.hideViewer(context); } }) .append('div') From 70a732953c0abae3c6c04bf372dd89b64b705f47 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Fri, 31 May 2024 12:09:54 +0200 Subject: [PATCH 05/44] added all images (and sequences) to map as flat pictures --- modules/services/panoramax.js | 84 ++++++++++++--------------------- modules/svg/panoramax_images.js | 5 +- 2 files changed, 33 insertions(+), 56 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 54e2509be..7737f121d 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -16,7 +16,7 @@ const tileUrl = apiUrl + 'api/map/{z}/{x}/{y}.pbf'; const pictureLayer = 'pictures'; const sequenceLayer = 'sequences'; -const minZoom = 14; +const minZoom = 15; const dispatch = d3_dispatch('loadedImages', 'loadedLines'); const imgZoom = d3_zoom() .extent([[0, 0], [320, 240]]) @@ -124,8 +124,6 @@ function loadTile(which, url, tile) { function loadTileDataToCache(data, tile) { const vectorTile = new VectorTile(new Protobuf(data)); - console.log(vectorTile) - let features, cache, layer, @@ -148,12 +146,13 @@ function loadTileDataToCache(data, tile) { capture_time: feature.properties.ts, id: feature.properties.id, acc_id: feature.properties.account_id, - sequence_id: feature.properties.sequences[0], + sequence_id: feature.properties.sequences.split("\"")[1], heading: feature.properties.heading, resolution: feature.properties.resolution, type: feature.properties.type, model: feature.properties.model, }; + console.log(d.sequence_id) cache.forImageId[d.id] = d; features.push({ minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d @@ -162,11 +161,9 @@ function loadTileDataToCache(data, tile) { if (cache.rtree) { cache.rtree.load(features); } - console.log(feature) } if (vectorTile.layers.hasOwnProperty(sequenceLayer)) { - features = []; cache = _cache.sequences; layer = vectorTile.layers[sequenceLayer]; @@ -182,23 +179,6 @@ function loadTileDataToCache(data, tile) { } -function getImageData(imageId, sequenceId) { - - return fetch(apiUrl + `api/collections/${sequenceId}/items`, {method: 'GET'}) - .then(function (response) { - if (!response.ok) { - throw new Error(response.status + ' ' + response.statusText); - } - return response.json(); - }) - .then(function (data) { - let index = data.data.findIndex((feature) => feature.id === imageId); - const {filename, uploaded_hash} = data.data[index]; - _sceneOptions.panorama = imageBaseUrl + '/' + uploaded_hash + '/' + filename + '/' + resolution; - }); -} - - export default { init: function() { if (!_cache) { @@ -236,13 +216,13 @@ export default { // Load images in the visible area loadImages: function(projection) { let url = tileUrl; - loadTiles('images', url, 14, projection); + loadTiles('images', url, 15, projection); }, // Load line in the visible area loadLines: function(projection) { let url = tileUrl; - loadTiles('line', url, 14, projection); + loadTiles('line', url, 15, projection); }, // Get visible sequences @@ -282,7 +262,6 @@ export default { } }, - // Update the currently highlighted sequence and selected bubble. setStyles: function(context, hovered) { const hoveredImageId = hovered && hovered.id; @@ -384,32 +363,31 @@ export default { .selectAll('button.forward') .classed('hide', !_cache.images.forImageId.hasOwnProperty(+id + 1)); - - getImageData(d.id,d.sequence_id).then(function () { - - if (d.type == "equirectangular") { - if (!_pannellumViewer) { - that.initViewer(); - } else { - // make a new scene - _currScene += 1; - let sceneID = _currScene.toString(); - _pannellumViewer - .addScene(sceneID, _sceneOptions) - .loadScene(sceneID); - - // remove previous scene - if (_currScene > 2) { - sceneID = (_currScene - 1).toString(); - _pannellumViewer - .removeScene(sceneID); - } - } + that.initOnlyPhoto(context, id); + /* + if (d.type == "equirectangular") { + if (!_pannellumViewer) { + that.initViewer(); } else { - // make non-panoramic photo viewer - that.initOnlyPhoto(context); + // make a new scene + _currScene += 1; + let sceneID = _currScene.toString(); + _pannellumViewer + .addScene(sceneID, _sceneOptions) + .loadScene(sceneID); + + // remove previous scene + if (_currScene > 2) { + sceneID = (_currScene - 1).toString(); + _pannellumViewer + .removeScene(sceneID); + } } - }); + } else { + // make non-panoramic photo viewer + that.initOnlyPhoto(context); + } + */ function localeDateString(s) { if (!s) return null; @@ -422,7 +400,7 @@ export default { return this; }, - initOnlyPhoto: function (context) { + initOnlyPhoto: function (context, id) { if (_pannellumViewer) { _pannellumViewer.destroy(); @@ -434,10 +412,10 @@ export default { let imgWrap = wrap.select('img'); if (!imgWrap.empty()) { - imgWrap.attr('src',_sceneOptions.panorama); + imgWrap.attr('src',apiUrl + 'api/pictures/' + id + '/sd.jpg'); } else { wrap.append('img') - .attr('src',_sceneOptions.panorama); + .attr('src',apiUrl + 'api/pictures/' + id + '/sd.jpg'); } }, diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 20d91ccf4..39d8a3e75 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -8,11 +8,10 @@ import {svgPath, svgPointTransform} from './helpers'; export function svgPanoramaxImages(projection, context, dispatch) { const throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); const minZoom = 12; + const viewFieldZoomLevel = 18; let layer = d3_select(null); let _panoramax; - const viewFieldZoomLevel = 18; - - + function init() { if (svgPanoramaxImages.initialized) return; svgPanoramaxImages.enabled = false; From 0732c01f8b7ed7a2a68dd1a0bb2a71e0cb825bba Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Mon, 3 Jun 2024 16:02:34 +0200 Subject: [PATCH 06/44] added modular image definition to backend --- modules/services/panoramax.js | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 7737f121d..6827dbfb9 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -12,6 +12,11 @@ import { localizer } from '../core/localizer'; const apiUrl = 'https://panoramax.openstreetmap.fr/'; const tileUrl = apiUrl + 'api/map/{z}/{x}/{y}.pbf'; +const imageUrl = apiUrl + 'api/pictures/{pictureID}/{definition}.jpg'; + +const highDefinition = "hd"; +const standardDefinition = "sd"; +const lowDefinition = "thumb"; const pictureLayer = 'pictures'; const sequenceLayer = 'sequences'; @@ -152,7 +157,6 @@ function loadTileDataToCache(data, tile) { type: feature.properties.type, model: feature.properties.model, }; - console.log(d.sequence_id) cache.forImageId[d.id] = d; features.push({ minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d @@ -312,6 +316,7 @@ export default { selectImage: function (context, id) { let that = this; + let url = imageUrl; let d = this.cachedImage(id); @@ -329,6 +334,9 @@ export default { let wrap = context.container().select('.photoviewer .panoramax-wrapper'); let attribution = wrap.selectAll('.photo-attribution').text(''); + const requestUrl = url.replace('{pictureID}', id) + .replace('{definition}', highDefinition); + if (d.capture_time) { attribution .append('span') @@ -344,7 +352,7 @@ export default { .append('a') .attr('class', 'image-link') .attr('target', '_blank') - .attr('href', apiUrl + 'api/pictures/' + d.id + '/hd.jpg') + .attr('href', requestUrl) .text('panoramax.fr'); wrap @@ -402,6 +410,19 @@ export default { initOnlyPhoto: function (context, id) { + // TODO: change it into a checkbox in UI + let isHighDefinition = true; + let definition = highDefinition; + + if(!isHighDefinition){ + definition = lowDefinition; + } + + let url = imageUrl; + + const requestUrl = url.replace('{pictureID}', id) + .replace('{definition}', definition); + if (_pannellumViewer) { _pannellumViewer.destroy(); _pannellumViewer = null; @@ -412,10 +433,10 @@ export default { let imgWrap = wrap.select('img'); if (!imgWrap.empty()) { - imgWrap.attr('src',apiUrl + 'api/pictures/' + id + '/sd.jpg'); + imgWrap.attr('src', requestUrl); } else { wrap.append('img') - .attr('src',apiUrl + 'api/pictures/' + id + '/sd.jpg'); + .attr('src', requestUrl); } }, From 9ac1dca235e517574a85209704781eb0806bea22 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Mon, 3 Jun 2024 17:20:58 +0200 Subject: [PATCH 07/44] wip, nothing to see here --- css/60_photos.css | 4 +-- modules/services/panoramax.js | 50 ++++++++++++++++------------------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/css/60_photos.css b/css/60_photos.css index 5c1708890..dcd567111 100644 --- a/css/60_photos.css +++ b/css/60_photos.css @@ -309,13 +309,13 @@ pointer-events: none; } .layer-panoramax .viewfield-group * { - fill: purple; + fill: #1234ae; stroke: #ffffff; stroke-opacity: .6; fill-opacity: .6; } .layer-panoramax .sequence { - stroke: purple; + stroke: #1234ae; } .photo-controls-panoramax { display: flex; diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 6827dbfb9..427758bf1 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -313,10 +313,22 @@ export default { _pannellumViewer = window.pannellum.viewer('ideditor-viewer-panoramax-pnlm', options); }, - selectImage: function (context, id) { + getImageUrl: function(id, definition){ + const requestUrl = imageUrl.replace('{pictureID}', id) + .replace('{definition}', definition); + return requestUrl; + }, + + selectImage: function (context, id) { let that = this; - let url = imageUrl; + + let isHighDefinition = true; + let definition = highDefinition; + + if(!isHighDefinition){ + definition = lowDefinition; + } let d = this.cachedImage(id); @@ -324,6 +336,8 @@ export default { this.updateUrlImage(d.id); + requestUrl = this.getImageUrl(d.id, highDefinition); + let viewer = context.container().select('.photoviewer'); if (!viewer.empty()) viewer.datum(d); @@ -334,9 +348,6 @@ export default { let wrap = context.container().select('.photoviewer .panoramax-wrapper'); let attribution = wrap.selectAll('.photo-attribution').text(''); - const requestUrl = url.replace('{pictureID}', id) - .replace('{definition}', highDefinition); - if (d.capture_time) { attribution .append('span') @@ -366,25 +377,23 @@ export default { wrap .selectAll('button.back') - .classed('hide', !_cache.images.forImageId.hasOwnProperty(+id - 1)); + .classed('hide', !_cache.images.forImageId.hasOwnProperty(+ id - 1)); wrap .selectAll('button.forward') - .classed('hide', !_cache.images.forImageId.hasOwnProperty(+id + 1)); + .classed('hide', !_cache.images.forImageId.hasOwnProperty(+ id + 1)); - that.initOnlyPhoto(context, id); - /* if (d.type == "equirectangular") { + _sceneOptions.type = "equirectangular"; + _sceneOptions.panorama = requestUrl; if (!_pannellumViewer) { that.initViewer(); } else { - // make a new scene _currScene += 1; let sceneID = _currScene.toString(); _pannellumViewer .addScene(sceneID, _sceneOptions) .loadScene(sceneID); - // remove previous scene if (_currScene > 2) { sceneID = (_currScene - 1).toString(); _pannellumViewer @@ -392,10 +401,8 @@ export default { } } } else { - // make non-panoramic photo viewer - that.initOnlyPhoto(context); + that.initOnlyPhoto(context, id, highDefinition); } - */ function localeDateString(s) { if (!s) return null; @@ -408,20 +415,9 @@ export default { return this; }, - initOnlyPhoto: function (context, id) { + initOnlyPhoto: function (context, id, definition) { - // TODO: change it into a checkbox in UI - let isHighDefinition = true; - let definition = highDefinition; - - if(!isHighDefinition){ - definition = lowDefinition; - } - - let url = imageUrl; - - const requestUrl = url.replace('{pictureID}', id) - .replace('{definition}', definition); + const requestUrl = this.getImageUrl(id, definition); if (_pannellumViewer) { _pannellumViewer.destroy(); From 70c5624d80a2dee9934c5cf5f34e179e45eb6e44 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Mon, 3 Jun 2024 22:46:36 +0200 Subject: [PATCH 08/44] changed API images endpoint to be able to move in sequence (wip) --- modules/services/panoramax.js | 48 +++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 427758bf1..936a02bc9 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -12,11 +12,12 @@ import { localizer } from '../core/localizer'; const apiUrl = 'https://panoramax.openstreetmap.fr/'; const tileUrl = apiUrl + 'api/map/{z}/{x}/{y}.pbf'; -const imageUrl = apiUrl + 'api/pictures/{pictureID}/{definition}.jpg'; +const imageBlobUrl = apiUrl + 'api/pictures/{pictureID}/{definition}.jpg'; +const imageDataUrl = apiUrl + 'api/collections/{collectionId}/items/{itemId}'; const highDefinition = "hd"; const standardDefinition = "sd"; -const lowDefinition = "thumb"; +const thumbnailDefinition = "thumb"; const pictureLayer = 'pictures'; const sequenceLayer = 'sequences'; @@ -313,13 +314,24 @@ export default { _pannellumViewer = window.pannellum.viewer('ideditor-viewer-panoramax-pnlm', options); }, - getImageUrl: function(id, definition){ - const requestUrl = imageUrl.replace('{pictureID}', id) + getImageBlob: function(image_id, definition){ + const requestUrl = imageBlobUrl.replace('{pictureID}', image_id) .replace('{definition}', definition); return requestUrl; }, + getImageData: async function(collection_id, image_id){ + const requestUrl = imageDataUrl.replace('{collectionId}', collection_id) + .replace('{itemId}', image_id) + + const response = await fetch(requestUrl, { method: 'GET' }); + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } + return await response.json(); + }, + selectImage: function (context, id) { let that = this; @@ -327,7 +339,7 @@ export default { let definition = highDefinition; if(!isHighDefinition){ - definition = lowDefinition; + definition = standardDefinition; } let d = this.cachedImage(id); @@ -336,7 +348,17 @@ export default { this.updateUrlImage(d.id); - requestUrl = this.getImageUrl(d.id, highDefinition); + let imageJSON = this.getImageData(d.sequence_id, d.id) + + console.log(imageJSON)["assets"]; + + let imageUrl = imageJSON["assets"]["hd"].href; + + let prevImageId = imageJSON.links.find(x => x.rel === 'prev').id; + let nextImageId = imageJSON.links.find(x => x.rel === 'next').id; + + console.log(prevImageId); + console.log(nextImageId); let viewer = context.container().select('.photoviewer'); if (!viewer.empty()) viewer.datum(d); @@ -363,7 +385,7 @@ export default { .append('a') .attr('class', 'image-link') .attr('target', '_blank') - .attr('href', requestUrl) + .attr('href', imageUrl) .text('panoramax.fr'); wrap @@ -384,7 +406,7 @@ export default { if (d.type == "equirectangular") { _sceneOptions.type = "equirectangular"; - _sceneOptions.panorama = requestUrl; + _sceneOptions.panorama = imageUrl; if (!_pannellumViewer) { that.initViewer(); } else { @@ -401,7 +423,7 @@ export default { } } } else { - that.initOnlyPhoto(context, id, highDefinition); + that.initOnlyPhoto(context, imageUrl); } function localeDateString(s) { @@ -415,9 +437,7 @@ export default { return this; }, - initOnlyPhoto: function (context, id, definition) { - - const requestUrl = this.getImageUrl(id, definition); + initOnlyPhoto: function (context, imageUrl) { if (_pannellumViewer) { _pannellumViewer.destroy(); @@ -429,10 +449,10 @@ export default { let imgWrap = wrap.select('img'); if (!imgWrap.empty()) { - imgWrap.attr('src', requestUrl); + imgWrap.attr('src', imageUrl); } else { wrap.append('img') - .attr('src', requestUrl); + .attr('src', imageUrl); } }, From 3a3b9bc4ff59c7e2784a52b61941195611391708 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Tue, 4 Jun 2024 03:14:17 +0200 Subject: [PATCH 09/44] move beetween pictures in sequence --- modules/services/panoramax.js | 111 +++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 41 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 936a02bc9..b48b9bc7b 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -46,6 +46,12 @@ let _sceneOptions = { }; let _currScene = 0; +let _currentScene = { + currentImage : null, + nextImage : null, + prevImage : null +}; + // Partition viewport into higher zoom tiles function partitionViewport(projection) { @@ -184,6 +190,59 @@ function loadTileDataToCache(data, tile) { } +function getImage(image_id, definition){ + const requestUrl = imageBlobUrl.replace('{pictureID}', image_id) + .replace('{definition}', definition); + + return requestUrl; +} + +function getImageData(collection_id, image_id, definition){ + const requestUrl = imageDataUrl.replace('{collectionId}', collection_id) + .replace('{itemId}', image_id) + + return fetch(requestUrl, { method: 'GET' }).then(function(response){ + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } + return response.json(); + }).then(function(data){ + _currentScene = { + currentImage : null, + nextImage : null, + prevImage : null + }; + + _currentScene.currentImage = data["assets"][definition]; + + // TODO: make it modular + _currentScene.nextImage = data.links[5]; + if(_currentScene.nextImage != null){ + if(_currentScene.nextImage.rel == "prev"){ + _currentScene.prevImage = data.links[5]; + _currentScene.nextImage = null; + } + else + _currentScene.prevImage = data.links[6]; + } + + _sceneOptions.panorama = _currentScene.currentImage.href; + + + // TODO: how to move this outta here + let wrap = context.container().select('.photoviewer .panoramax-wrapper'); + + wrap + .selectAll('button.back') + .classed('hide', _currentScene.prevImage == null); + wrap + .selectAll('button.forward') + .classed('hide', _currentScene.nextImage == null); + + return data; + }); +} + export default { init: function() { if (!_cache) { @@ -314,24 +373,6 @@ export default { _pannellumViewer = window.pannellum.viewer('ideditor-viewer-panoramax-pnlm', options); }, - getImageBlob: function(image_id, definition){ - const requestUrl = imageBlobUrl.replace('{pictureID}', image_id) - .replace('{definition}', definition); - - return requestUrl; - }, - - getImageData: async function(collection_id, image_id){ - const requestUrl = imageDataUrl.replace('{collectionId}', collection_id) - .replace('{itemId}', image_id) - - const response = await fetch(requestUrl, { method: 'GET' }); - if (!response.ok) { - throw new Error(response.status + ' ' + response.statusText); - } - return await response.json(); - }, - selectImage: function (context, id) { let that = this; @@ -348,17 +389,9 @@ export default { this.updateUrlImage(d.id); - let imageJSON = this.getImageData(d.sequence_id, d.id) + getImageData(d.sequence_id, d.id, definition); - console.log(imageJSON)["assets"]; - - let imageUrl = imageJSON["assets"]["hd"].href; - - let prevImageId = imageJSON.links.find(x => x.rel === 'prev').id; - let nextImageId = imageJSON.links.find(x => x.rel === 'next').id; - - console.log(prevImageId); - console.log(nextImageId); + let imageUrl = getImage(d.id, highDefinition); let viewer = context.container().select('.photoviewer'); if (!viewer.empty()) viewer.datum(d); @@ -396,17 +429,9 @@ export default { wrap .selectAll('img') .remove(); - - wrap - .selectAll('button.back') - .classed('hide', !_cache.images.forImageId.hasOwnProperty(+ id - 1)); - wrap - .selectAll('button.forward') - .classed('hide', !_cache.images.forImageId.hasOwnProperty(+ id + 1)); - + if (d.type == "equirectangular") { _sceneOptions.type = "equirectangular"; - _sceneOptions.panorama = imageUrl; if (!_pannellumViewer) { that.initViewer(); } else { @@ -565,12 +590,16 @@ export default { function step(stepBy) { return function () { if (!_activeImage) return; - const imageId = _activeImage.id; - const nextIndex = imageId + stepBy; - if (!nextIndex) return; + let nextId; + if(stepBy === 1) + nextId = _currentScene.nextImage.id; + else nextId = _currentScene.prevImage.id; - const nextImage = _cache.images.forImageId[nextIndex]; + if (!nextId) return; + + + const nextImage = _cache.images.forImageId[nextId]; context.map().centerEase(nextImage.loc); From 53ed8f3e52f56d4cb4dd6d60fa11b48c217f91b9 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Tue, 4 Jun 2024 14:14:01 +0200 Subject: [PATCH 10/44] added 360 photos --- modules/services/panoramax.js | 128 ++++++++++++++------------------ modules/svg/panoramax_images.js | 2 +- 2 files changed, 58 insertions(+), 72 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index b48b9bc7b..6009bdf47 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -131,8 +131,6 @@ function loadTile(which, url, tile) { }); } - -// Load the data from the vector tile into cache function loadTileDataToCache(data, tile) { const vectorTile = new VectorTile(new Protobuf(data)); @@ -190,6 +188,7 @@ function loadTileDataToCache(data, tile) { } +// Quick access to image function getImage(image_id, definition){ const requestUrl = imageBlobUrl.replace('{pictureID}', image_id) .replace('{definition}', definition); @@ -197,50 +196,16 @@ function getImage(image_id, definition){ return requestUrl; } -function getImageData(collection_id, image_id, definition){ +async function getImageData(collection_id, image_id, definition){ const requestUrl = imageDataUrl.replace('{collectionId}', collection_id) .replace('{itemId}', image_id) - return fetch(requestUrl, { method: 'GET' }).then(function(response){ - if (!response.ok) { - throw new Error(response.status + ' ' + response.statusText); - } - return response.json(); - }).then(function(data){ - _currentScene = { - currentImage : null, - nextImage : null, - prevImage : null - }; - - _currentScene.currentImage = data["assets"][definition]; - - // TODO: make it modular - _currentScene.nextImage = data.links[5]; - if(_currentScene.nextImage != null){ - if(_currentScene.nextImage.rel == "prev"){ - _currentScene.prevImage = data.links[5]; - _currentScene.nextImage = null; - } - else - _currentScene.prevImage = data.links[6]; - } - - _sceneOptions.panorama = _currentScene.currentImage.href; - - - // TODO: how to move this outta here - let wrap = context.container().select('.photoviewer .panoramax-wrapper'); - - wrap - .selectAll('button.back') - .classed('hide', _currentScene.prevImage == null); - wrap - .selectAll('button.forward') - .classed('hide', _currentScene.nextImage == null); - - return data; - }); + const response = await fetch(requestUrl, { method: 'GET' }); + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } + const data = await response.json(); + return data; } export default { @@ -279,14 +244,12 @@ export default { // Load images in the visible area loadImages: function(projection) { - let url = tileUrl; - loadTiles('images', url, 15, projection); + loadTiles('images', tileUrl, 15, projection); }, // Load line in the visible area loadLines: function(projection) { - let url = tileUrl; - loadTiles('line', url, 15, projection); + loadTiles('line', tileUrl, 15, projection); }, // Get visible sequences @@ -389,8 +352,6 @@ export default { this.updateUrlImage(d.id); - getImageData(d.sequence_id, d.id, definition); - let imageUrl = getImage(d.id, highDefinition); let viewer = context.container().select('.photoviewer'); @@ -429,27 +390,50 @@ export default { wrap .selectAll('img') .remove(); - - if (d.type == "equirectangular") { - _sceneOptions.type = "equirectangular"; - if (!_pannellumViewer) { - that.initViewer(); - } else { - _currScene += 1; - let sceneID = _currScene.toString(); - _pannellumViewer - .addScene(sceneID, _sceneOptions) - .loadScene(sceneID); - if (_currScene > 2) { - sceneID = (_currScene - 1).toString(); + getImageData(d.sequence_id, d.id, definition).then(function(data){ + _currentScene = { + currentImage: null, + nextImage: null, + prevImage: null + }; + _currentScene.currentImage = data["assets"][definition]; + const nextIndex = data.links.findIndex(x => x.rel == "next"); + const prevIndex = data.links.findIndex(x_1 => x_1.rel == "prev"); + if (nextIndex != -1) + _currentScene.nextImage = data.links[nextIndex]; + if (prevIndex != -1) + _currentScene.prevImage = data.links[prevIndex]; + _sceneOptions.panorama = _currentScene.currentImage.href; + + wrap + .selectAll('button.back') + .classed('hide', _currentScene.prevImage == null); + wrap + .selectAll('button.forward') + .classed('hide', _currentScene.nextImage == null); + + if (d.type == "equirectangular") { + _sceneOptions.type = "equirectangular"; + if (!_pannellumViewer) { + that.initViewer(); + } else { + _currScene += 1; + let sceneID = _currScene.toString(); _pannellumViewer - .removeScene(sceneID); + .addScene(sceneID, _sceneOptions) + .loadScene(sceneID); + + if (_currScene > 2) { + sceneID = (_currScene - 1).toString(); + _pannellumViewer + .removeScene(sceneID); + } } + } else { + that.initOnlyPhoto(context, imageUrl); } - } else { - that.initOnlyPhoto(context, imageUrl); - } + }); function localeDateString(s) { if (!s) return null; @@ -462,24 +446,24 @@ export default { return this; }, - initOnlyPhoto: function (context, imageUrl) { + initOnlyPhoto: function(context, imageUrl) { if (_pannellumViewer) { _pannellumViewer.destroy(); _pannellumViewer = null; } - + let wrap = context.container().select('#ideditor-viewer-panoramax-simple'); - + let imgWrap = wrap.select('img'); - + if (!imgWrap.empty()) { imgWrap.attr('src', imageUrl); } else { wrap.append('img') .attr('src', imageUrl); } - + }, ensureViewerLoaded: function(context) { @@ -497,6 +481,7 @@ export default { let wrap = context.container().select('.photoviewer').selectAll('.panoramax-wrapper') .data([0]); + //TODO maybe all of this should be in panoramax_images? let wrapEnter = wrap.enter() .append('div') .attr('class', 'photo-wrapper panoramax-wrapper') @@ -587,6 +572,7 @@ export default { _loadViewerPromise = null; }); + //TODO: maybe this should be here (export?) function step(stepBy) { return function () { if (!_activeImage) return; diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 39d8a3e75..135bfc801 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -7,7 +7,7 @@ import {svgPath, svgPointTransform} from './helpers'; export function svgPanoramaxImages(projection, context, dispatch) { const throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); - const minZoom = 12; + const minZoom = 15; const viewFieldZoomLevel = 18; let layer = d3_select(null); let _panoramax; From 25888c52abfdc468709d486e5da8891ab53b2dc0 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Wed, 5 Jun 2024 20:43:42 +0200 Subject: [PATCH 11/44] added report button --- modules/services/panoramax.js | 24 +++++++++++++++++------- modules/svg/panoramax_images.js | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 6009bdf47..8572735f8 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -339,19 +339,19 @@ export default { selectImage: function (context, id) { let that = this; - let isHighDefinition = true; - let definition = highDefinition; - - if(!isHighDefinition){ - definition = standardDefinition; - } - let d = this.cachedImage(id); this.setActiveImage(d); this.updateUrlImage(d.id); + let isHighDefinition = true; + let definition = highDefinition; + + if(d.type == "equirectangular"){ + definition = standardDefinition; + } + let imageUrl = getImage(d.id, highDefinition); let viewer = context.container().select('.photoviewer'); @@ -375,6 +375,16 @@ export default { .text('|'); } + attribution + .append('a') + .attr('class', 'report-photo') + .attr('href', "mailto:signalement.ign@panoramax.fr") + .text('Report'); + + attribution + .append('span') + .text('|'); + attribution .append('a') .attr('class', 'image-link') diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 135bfc801..a98a1716e 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -174,7 +174,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { .attr('d', viewfieldPath); function viewfieldPath() { - if (this.parentNode.__data__.isPano) { + if (this.parentNode.__data__.type == "equirectangular") { return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; } else { return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z'; From 9e800af60a2edc133dcbb41bf076b6db2131d511 Mon Sep 17 00:00:00 2001 From: Mattia Pezzotti Date: Wed, 5 Jun 2024 23:44:56 +0200 Subject: [PATCH 12/44] hd work wip --- modules/services/panoramax.js | 47 +++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 8572735f8..42944ecdd 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -6,7 +6,7 @@ import Protobuf from 'pbf'; import RBush from 'rbush'; import { VectorTile } from '@mapbox/vector-tile'; -import { utilRebind, utilTiler, utilQsString, utilStringQs, utilSetTransform } from '../util'; +import { utilRebind, utilTiler, utilQsString, utilStringQs, utilSetTransform, utilUniqueDomId} from '../util'; import { geoExtent, geoScaleToZoom } from '../geo'; import { localizer } from '../core/localizer'; @@ -36,6 +36,8 @@ let _activeImage; let _cache; let _loadViewerPromise; let _pannellumViewer; +let _definition = standardDefinition; +let _isHD = false; let _sceneOptions = { showFullscreenCtrl: false, autoLoad: true, @@ -196,7 +198,7 @@ function getImage(image_id, definition){ return requestUrl; } -async function getImageData(collection_id, image_id, definition){ +async function getImageData(collection_id, image_id){ const requestUrl = imageDataUrl.replace('{collectionId}', collection_id) .replace('{itemId}', image_id) @@ -345,13 +347,6 @@ export default { this.updateUrlImage(d.id); - let isHighDefinition = true; - let definition = highDefinition; - - if(d.type == "equirectangular"){ - definition = standardDefinition; - } - let imageUrl = getImage(d.id, highDefinition); let viewer = context.container().select('.photoviewer'); @@ -392,6 +387,36 @@ export default { .attr('href', imageUrl) .text('panoramax.fr'); + let line1 = attribution + .append('div') + .attr('class', 'attribution-row'); + + const hiresDomId = utilUniqueDomId('panoramax-hd'); + + let label = line1 + .append('label') + .attr('for', hiresDomId) + .attr('class', 'panoramax-hd'); + + label + .append('input') + .attr('type', 'checkbox') + .attr('id', hiresDomId) + .property('checked', _isHD) + .on('click', (d3_event) => { + d3_event.stopPropagation(); + + _isHD = !_isHD; + _definition = _isHD ? highDefinition : standardDefinition; + wrap.call(setupCanvas, true); + + that.selectImage(context, d.id) + .showViewer(context); + }); + + label + .append('span'); + wrap .transition() .duration(100) @@ -401,13 +426,13 @@ export default { .selectAll('img') .remove(); - getImageData(d.sequence_id, d.id, definition).then(function(data){ + getImageData(d.sequence_id, d.id).then(function(data){ _currentScene = { currentImage: null, nextImage: null, prevImage: null }; - _currentScene.currentImage = data["assets"][definition]; + _currentScene.currentImage = data["assets"][_definition]; const nextIndex = data.links.findIndex(x => x.rel == "next"); const prevIndex = data.links.findIndex(x_1 => x_1.rel == "prev"); if (nextIndex != -1) From 2cdf24e8044a286b34f56b24acd5bb2808acbe66 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Sat, 8 Jun 2024 17:50:24 +0200 Subject: [PATCH 13/44] 360 photos now keep heading between sequences, added hd checkbox --- css/60_photos.css | 13 ++++ data/core.yaml | 2 + modules/services/panoramax.js | 115 ++++++++++++++++++++------------ modules/svg/panoramax_images.js | 14 +++- 4 files changed, 100 insertions(+), 44 deletions(-) diff --git a/css/60_photos.css b/css/60_photos.css index dcd567111..9f0893a53 100644 --- a/css/60_photos.css +++ b/css/60_photos.css @@ -349,6 +349,19 @@ overflow: hidden } +label.panoramax-hd { + float: left; + cursor: pointer; +} +.panoramax-hd span { + margin-top: 2px; +} +.panoramax-hd input[type="checkbox"] { + width: 12px; + height: 12px; + margin: 0 5px; +} + /* Streetside Viewer (pannellum) */ .ms-wrapper .photo-attribution .image-link { diff --git a/data/core.yaml b/data/core.yaml index f0ed90d24..806488a52 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -1452,6 +1452,8 @@ en: panoramax: title: Panoramax tooltip: "Street-level photos from Panoramax" + report: "Report" + hd: "High resolution" street_side: minzoom_tooltip: "Zoom in to see street-side photos" local_photos: diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 42944ecdd..da82cfd31 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -8,7 +8,7 @@ import { VectorTile } from '@mapbox/vector-tile'; import { utilRebind, utilTiler, utilQsString, utilStringQs, utilSetTransform, utilUniqueDomId} from '../util'; import { geoExtent, geoScaleToZoom } from '../geo'; -import { localizer } from '../core/localizer'; +import { t, localizer } from '../core/localizer'; const apiUrl = 'https://panoramax.openstreetmap.fr/'; const tileUrl = apiUrl + 'api/map/{z}/{x}/{y}.pbf'; @@ -308,6 +308,18 @@ export default { sequences.classed('highlighted', function(d) { return d.properties.sequence_id === hoveredSequenceId; }) .classed('currentView', function(d) { return d.properties.sequence_id === selectedSequenceId; }); + // update viewfields if needed + context.container().selectAll('.layer-streetside-images .viewfield-group .viewfield') + .attr('d', viewfieldPath); + + function viewfieldPath() { + if (this.parentNode.__data__.type == "equirectangular") { + return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; + } else { + return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z'; + } + } + return this; }, @@ -359,6 +371,51 @@ export default { let wrap = context.container().select('.photoviewer .panoramax-wrapper'); let attribution = wrap.selectAll('.photo-attribution').text(''); + let line1 = attribution + .append('div') + + const hiresDomId = utilUniqueDomId('panoramax-hd'); + + let label = line1 + .append('label') + .attr('for', hiresDomId) + .attr('class', 'panoramax-hd'); + + label + .append('input') + .attr('type', 'checkbox') + .attr('id', hiresDomId) + .property('checked', _isHD) + .on('click', (d3_event) => { + d3_event.stopPropagation(); + + _isHD = !_isHD; + _definition = _isHD ? highDefinition : standardDefinition; + + let viewstate = { + yaw: _pannellumViewer.getYaw(), + pitch: _pannellumViewer.getPitch(), + hfov: _pannellumViewer.getHfov() + }; + + _sceneOptions = Object.assign(_sceneOptions, viewstate); + that.selectImage(context, d.id) + .showViewer(context); + }); + + label + .append('span') + .call(t.append('panoramax.hd')); + + wrap + .transition() + .duration(100) + .call(imgZoom.transform, d3_zoomIdentity); + + wrap + .selectAll('img') + .remove(); + if (d.capture_time) { attribution .append('span') @@ -374,7 +431,7 @@ export default { .append('a') .attr('class', 'report-photo') .attr('href', "mailto:signalement.ign@panoramax.fr") - .text('Report'); + .call(t.append('panoramax.report')); attribution .append('span') @@ -387,45 +444,6 @@ export default { .attr('href', imageUrl) .text('panoramax.fr'); - let line1 = attribution - .append('div') - .attr('class', 'attribution-row'); - - const hiresDomId = utilUniqueDomId('panoramax-hd'); - - let label = line1 - .append('label') - .attr('for', hiresDomId) - .attr('class', 'panoramax-hd'); - - label - .append('input') - .attr('type', 'checkbox') - .attr('id', hiresDomId) - .property('checked', _isHD) - .on('click', (d3_event) => { - d3_event.stopPropagation(); - - _isHD = !_isHD; - _definition = _isHD ? highDefinition : standardDefinition; - wrap.call(setupCanvas, true); - - that.selectImage(context, d.id) - .showViewer(context); - }); - - label - .append('span'); - - wrap - .transition() - .duration(100) - .call(imgZoom.transform, d3_zoomIdentity); - - wrap - .selectAll('img') - .remove(); - getImageData(d.sequence_id, d.id).then(function(data){ _currentScene = { currentImage: null, @@ -619,6 +637,13 @@ export default { if (!nextId) return; + let viewstate = { + yaw: _pannellumViewer.getYaw(), + pitch: _pannellumViewer.getPitch(), + hfov: _pannellumViewer.getHfov() + }; + + _sceneOptions = Object.assign(_sceneOptions, viewstate); const nextImage = _cache.images.forImageId[nextId]; @@ -637,7 +662,13 @@ export default { return _loadViewerPromise; }, - showViewer:function (context) { + yaw: function(yaw) { + if (typeof yaw !== 'number') return yaw; + _sceneOptions.yaw = yaw; + return this; + }, + + showViewer: function (context) { let wrap = context.container().select('.photoviewer') .classed('hide', false); diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index a98a1716e..7dda1a31c 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -11,6 +11,8 @@ export function svgPanoramaxImages(projection, context, dispatch) { const viewFieldZoomLevel = 18; let layer = d3_select(null); let _panoramax; + let _viewerYaw = 0; + let _selectedSequence; function init() { if (svgPanoramaxImages.initialized) return; @@ -58,8 +60,9 @@ export function svgPanoramaxImages(projection, context, dispatch) { function transform(d) { let t = svgPointTransform(projection)(d); - if (d.heading) { - t += ' rotate(' + Math.floor(d.heading) + ',0,0)'; + var rot = d.heading + _viewerYaw; + if (rot) { + t += ' rotate(' + Math.floor(rot) + ',0,0)'; } return t; } @@ -79,11 +82,18 @@ export function svgPanoramaxImages(projection, context, dispatch) { const service = getService(); if (!service) return; + if (image.sequence_id !== _selectedSequence) { + _viewerYaw = 0; // reset + } + + _selectedSequence = image.sequence_id; + service .ensureViewerLoaded(context, image.id) .then(function() { service .selectImage(context, image.id) + .yaw(_viewerYaw) .showViewer(context); }); From 3ac71518967d68732b10d1fadcc098e7d683f920 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Tue, 11 Jun 2024 00:24:19 +0200 Subject: [PATCH 14/44] added viewfield heading --- modules/services/panoramax.js | 55 ++++++++++++++++++++++++++------- modules/svg/panoramax_images.js | 24 ++++++++++++-- modules/svg/streetside.js | 37 ++++++++++------------ 3 files changed, 82 insertions(+), 34 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index da82cfd31..c2367591a 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -1,5 +1,6 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; import { select as d3_select } from 'd3-selection'; +import { timer as d3_timer } from 'd3-timer'; import { zoom as d3_zoom, zoomIdentity as d3_zoomIdentity } from 'd3-zoom'; import Protobuf from 'pbf'; @@ -23,7 +24,7 @@ const pictureLayer = 'pictures'; const sequenceLayer = 'sequences'; const minZoom = 15; -const dispatch = d3_dispatch('loadedImages', 'loadedLines'); +const dispatch = d3_dispatch('loadedImages', 'loadedLines', 'viewerChanged'); const imgZoom = d3_zoom() .extent([[0, 0], [320, 240]]) .translateExtent([[0, 0], [320, 240]]) @@ -301,24 +302,27 @@ export default { const markers = context.container().selectAll('.layer-panoramax .viewfield-group'); const sequences = context.container().selectAll('.layer-panoramax .sequence'); - markers.classed('highlighted', function(d) { return d.id === hoveredImageId; }) + markers + .classed('highlighted', function(d) { return d.id === hoveredImageId; }) .classed('hovered', function(d) { return d.id === hoveredImageId; }) .classed('currentView', function(d) { return d.id === selectedImageId; }); - sequences.classed('highlighted', function(d) { return d.properties.sequence_id === hoveredSequenceId; }) - .classed('currentView', function(d) { return d.properties.sequence_id === selectedSequenceId; }); + sequences + .classed('highlighted', function(d) { return d.sequence_id === hoveredSequenceId; }) + .classed('currentView', function(d) { return d.sequence_id === selectedSequenceId; }); // update viewfields if needed - context.container().selectAll('.layer-streetside-images .viewfield-group .viewfield') + context.container().selectAll('.layer-panoramax .viewfield-group .viewfield') .attr('d', viewfieldPath); function viewfieldPath() { - if (this.parentNode.__data__.type == "equirectangular") { + let d = this.parentNode.__data__; + if (d.type == "equirectangular" && d.id !== selectedImageId) { return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; } else { return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z'; + } } - } return this; }, @@ -373,18 +377,19 @@ export default { let line1 = attribution .append('div') + .attr('class', 'attribution-row'); - const hiresDomId = utilUniqueDomId('panoramax-hd'); + const hdDomId = utilUniqueDomId('panoramax-hd'); let label = line1 .append('label') - .attr('for', hiresDomId) + .attr('for', hdDomId) .attr('class', 'panoramax-hd'); label .append('input') .attr('type', 'checkbox') - .attr('id', hiresDomId) + .attr('id', hdDomId) .property('checked', _isHD) .on('click', (d3_event) => { d3_event.stopPropagation(); @@ -452,7 +457,7 @@ export default { }; _currentScene.currentImage = data["assets"][_definition]; const nextIndex = data.links.findIndex(x => x.rel == "next"); - const prevIndex = data.links.findIndex(x_1 => x_1.rel == "prev"); + const prevIndex = data.links.findIndex(x => x.rel == "prev"); if (nextIndex != -1) _currentScene.nextImage = data.links[nextIndex]; if (prevIndex != -1) @@ -519,6 +524,10 @@ export default { }, + viewer: function() { + return _pannellumViewer; + }, + ensureViewerLoaded: function(context) { let that = this; @@ -541,9 +550,31 @@ export default { .classed('hide', true) .on('dblclick.zoom', null); + let pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; + wrapEnter .append('div') - .attr('class', 'photo-attribution fillD'); + .attr('id', 'ideditor-viewer-panoramax-pnlm') + .on(pointerPrefix + 'down.panoramax', () => { + d3_select(window) + .on(pointerPrefix + 'move.panoramax', () => { + dispatch.call('viewerChanged'); + }, true); + }) + .on(pointerPrefix + 'up.panoramax pointercancel.panoramax', () => { + d3_select(window) + .on(pointerPrefix + 'move.panoramax', null); + + // continue dispatching events for a few seconds, in case viewer has inertia. + let t = d3_timer(elapsed => { + dispatch.call('viewerChanged'); + if (elapsed > 2000) { + t.stop(); + } + }); + }) + .append('div') + .attr('class', 'photo-attribution fillD'); const controlsEnter = wrapEnter .append('div') diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 7dda1a31c..5437242d8 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -24,7 +24,9 @@ export function svgPanoramaxImages(projection, context, dispatch) { function getService() { if (services.panoramax && !_panoramax) { _panoramax = services.panoramax; - _panoramax.event.on('loadedImages', throttledRedraw); + _panoramax.event + .on('viewerChanged', viewerChanged) + .on('loadedImages', throttledRedraw); } else if (!services.panoramax && _panoramax) { _panoramax = null; } @@ -121,7 +123,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { let images = (service ? service.images(projection) : []); let traces = layer.selectAll('.sequences').selectAll('.sequence') - .data(sequences, function(d) { return d.properties.id; }); + .data(sequences, function(d) { return d.id; }); // exit traces.exit() @@ -193,6 +195,24 @@ export function svgPanoramaxImages(projection, context, dispatch) { } + function viewerChanged() { + var service = getService(); + if (!service) return; + + var viewer = service.viewer(); + if (!viewer) return; + + // update viewfield rotation + _viewerYaw = viewer.getYaw(); + + // avoid updating if the map is currently transformed + // e.g. during drags or easing. + if (context.map().isTransformed()) return; + + layer.selectAll('.viewfield-group.currentView') + .attr('transform', transform); + } + function drawImages(selection) { const enabled = svgPanoramaxImages.enabled; diff --git a/modules/svg/streetside.js b/modules/svg/streetside.js index 73ec4eeb7..9279c33a8 100644 --- a/modules/svg/streetside.js +++ b/modules/svg/streetside.js @@ -137,26 +137,6 @@ export function svgStreetside(projection, context, dispatch) { return t; } - - function viewerChanged() { - var service = getService(); - if (!service) return; - - var viewer = service.viewer(); - if (!viewer) return; - - // update viewfield rotation - _viewerYaw = viewer.getYaw(); - - // avoid updating if the map is currently transformed - // e.g. during drags or easing. - if (context.map().isTransformed()) return; - - layer.selectAll('.viewfield-group.currentView') - .attr('transform', transform); - } - - function filterBubbles(bubbles) { var fromDate = context.photos().fromDate(); var toDate = context.photos().toDate(); @@ -309,7 +289,24 @@ export function svgStreetside(projection, context, dispatch) { return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z'; } } + } + function viewerChanged() { + var service = getService(); + if (!service) return; + + var viewer = service.viewer(); + if (!viewer) return; + + // update viewfield rotation + _viewerYaw = viewer.getYaw(); + + // avoid updating if the map is currently transformed + // e.g. during drags or easing. + if (context.map().isTransformed()) return; + + layer.selectAll('.viewfield-group.currentView') + .attr('transform', transform); } /** From baa426d594d1736ec6fba5033790037655e60aec Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Tue, 11 Jun 2024 00:26:21 +0200 Subject: [PATCH 15/44] reverted streetside changes (oops!) --- modules/svg/streetside.js | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/modules/svg/streetside.js b/modules/svg/streetside.js index 9279c33a8..73ec4eeb7 100644 --- a/modules/svg/streetside.js +++ b/modules/svg/streetside.js @@ -137,6 +137,26 @@ export function svgStreetside(projection, context, dispatch) { return t; } + + function viewerChanged() { + var service = getService(); + if (!service) return; + + var viewer = service.viewer(); + if (!viewer) return; + + // update viewfield rotation + _viewerYaw = viewer.getYaw(); + + // avoid updating if the map is currently transformed + // e.g. during drags or easing. + if (context.map().isTransformed()) return; + + layer.selectAll('.viewfield-group.currentView') + .attr('transform', transform); + } + + function filterBubbles(bubbles) { var fromDate = context.photos().fromDate(); var toDate = context.photos().toDate(); @@ -289,24 +309,7 @@ export function svgStreetside(projection, context, dispatch) { return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z'; } } - } - function viewerChanged() { - var service = getService(); - if (!service) return; - - var viewer = service.viewer(); - if (!viewer) return; - - // update viewfield rotation - _viewerYaw = viewer.getYaw(); - - // avoid updating if the map is currently transformed - // e.g. during drags or easing. - if (context.map().isTransformed()) return; - - layer.selectAll('.viewfield-group.currentView') - .attr('transform', transform); } /** From 15b1f6111e03ab100c6c3adf2d43f40fc2e75fad Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Thu, 13 Jun 2024 00:55:21 +0200 Subject: [PATCH 16/44] added date and photo type filter --- modules/renderer/photos.js | 4 +-- modules/services/panoramax.js | 8 ++--- modules/svg/panoramax_images.js | 56 ++++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/modules/renderer/photos.js b/modules/renderer/photos.js index 4050ff42c..f2a8de21c 100644 --- a/modules/renderer/photos.js +++ b/modules/renderer/photos.js @@ -119,12 +119,12 @@ export function rendererPhotos(context) { } photos.shouldFilterByDate = function() { - return showsLayer('mapillary') || showsLayer('kartaview') || showsLayer('streetside') || showsLayer('vegbilder'); + return showsLayer('mapillary') || showsLayer('kartaview') || showsLayer('streetside') || showsLayer('vegbilder') || showsLayer('panoramax'); }; photos.shouldFilterByPhotoType = function() { return showsLayer('mapillary') || - (showsLayer('streetside') && showsLayer('kartaview')) || showsLayer('vegbilder'); + (showsLayer('streetside') && showsLayer('kartaview')) || showsLayer('vegbilder') || showsLayer('panoramax'); }; photos.shouldFilterByUsername = function() { diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index c2367591a..fb8637dd8 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -162,7 +162,7 @@ function loadTileDataToCache(data, tile) { sequence_id: feature.properties.sequences.split("\"")[1], heading: feature.properties.heading, resolution: feature.properties.resolution, - type: feature.properties.type, + isPano: feature.properties.type === "equirectangular", model: feature.properties.model, }; cache.forImageId[d.id] = d; @@ -317,7 +317,7 @@ export default { function viewfieldPath() { let d = this.parentNode.__data__; - if (d.type == "equirectangular" && d.id !== selectedImageId) { + if (d.isPano && d.id !== selectedImageId) { return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; } else { return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z'; @@ -471,7 +471,7 @@ export default { .selectAll('button.forward') .classed('hide', _currentScene.nextImage == null); - if (d.type == "equirectangular") { + if (d.isPano) { _sceneOptions.type = "equirectangular"; if (!_pannellumViewer) { that.initViewer(); @@ -568,7 +568,7 @@ export default { // continue dispatching events for a few seconds, in case viewer has inertia. let t = d3_timer(elapsed => { dispatch.call('viewerChanged'); - if (elapsed > 2000) { + if (elapsed > 1000) { t.stop(); } }); diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 5437242d8..ea0ab96cb 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -34,6 +34,57 @@ export function svgPanoramaxImages(projection, context, dispatch) { return _panoramax; } + function filterImages(images) { + const showsPano = context.photos().showsPanoramic(); + const showsFlat = context.photos().showsFlat(); + const fromDate = context.photos().fromDate(); + const toDate = context.photos().toDate(); + + if (!showsPano || !showsFlat) { + images = images.filter(function(image) { + if (image.isPano) return showsPano; + return showsFlat; + }); + } + if (fromDate) { + images = images.filter(function(image) { + return new Date(image.capture_time).getTime() >= new Date(fromDate).getTime(); + }); + } + if (toDate) { + images = images.filter(function(image) { + return new Date(image.capture_time).getTime() <= new Date(toDate).getTime(); + }); + } + + return images; + } + + function filterSequences(sequences) { + const showsPano = context.photos().showsPanoramic(); + const showsFlat = context.photos().showsFlat(); + const fromDate = context.photos().fromDate(); + const toDate = context.photos().toDate(); + + if (!showsPano || !showsFlat) { + sequences = sequences.filter(function(sequence) { + if (sequence.properties.type === "equirectangular") return showsPano; + return showsFlat; + }); + } + if (fromDate) { + sequences = sequences.filter(function(sequence) { + return new Date(sequence.properties.date).getTime() >= new Date(fromDate).getTime().toString(); + }); + } + if (toDate) { + sequences = sequences.filter(function(sequence) { + return new Date(sequence.properties.date).getTime() <= new Date(toDate).getTime().toString(); + }); + } + + return sequences; + } function showLayer() { const service = getService(); @@ -122,6 +173,9 @@ export function svgPanoramaxImages(projection, context, dispatch) { let sequences = (service ? service.sequences(projection) : []); let images = (service ? service.images(projection) : []); + images = filterImages(images); + sequences = filterSequences(sequences, service); + let traces = layer.selectAll('.sequences').selectAll('.sequence') .data(sequences, function(d) { return d.id; }); @@ -186,7 +240,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { .attr('d', viewfieldPath); function viewfieldPath() { - if (this.parentNode.__data__.type == "equirectangular") { + if (this.parentNode.__data__.isPano) { return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; } else { return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z'; From 6467e9efe39d45d91fa93cf577f3b6a1cfb54b68 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Thu, 13 Jun 2024 17:02:57 +0200 Subject: [PATCH 17/44] added draw sequences lines on lower zoom levels --- modules/services/panoramax.js | 17 ++++++++--------- modules/svg/panoramax_images.js | 28 ++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index fb8637dd8..960575716 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -23,7 +23,7 @@ const thumbnailDefinition = "thumb"; const pictureLayer = 'pictures'; const sequenceLayer = 'sequences'; -const minZoom = 15; +const minZoom = 10; const dispatch = d3_dispatch('loadedImages', 'loadedLines', 'viewerChanged'); const imgZoom = d3_zoom() .extent([[0, 0], [320, 240]]) @@ -33,7 +33,6 @@ const pannellumViewerCSS = 'pannellum/pannellum.css'; const pannellumViewerJS = 'pannellum/pannellum.js'; const resolution = 1080; -let _activeImage; let _cache; let _loadViewerPromise; let _pannellumViewer; @@ -231,7 +230,7 @@ export default { requests: { loaded: {}, inflight: {} } }; - _activeImage = null; + _currentScene.currentImage = null; }, // Get visible images @@ -252,7 +251,7 @@ export default { // Load line in the visible area loadLines: function(projection) { - loadTiles('line', tileUrl, 15, projection); + loadTiles('line', tileUrl, 10, projection); }, // Get visible sequences @@ -283,12 +282,12 @@ export default { // Set the currently visible image setActiveImage: function(image) { if (image) { - _activeImage = { + _currentScene.currentImage = { id: image.id, sequence_id: image.sequence_id }; } else { - _activeImage = null; + _currentScene.currentImage = null; } }, @@ -296,8 +295,8 @@ export default { setStyles: function(context, hovered) { const hoveredImageId = hovered && hovered.id; const hoveredSequenceId = hovered && hovered.sequence_id; - const selectedSequenceId = _activeImage && _activeImage.sequence_id; - const selectedImageId = _activeImage && _activeImage.id; + const selectedSequenceId = _currentScene.currentImage && _currentScene.currentImage.sequence_id; + const selectedImageId = _currentScene.currentImage && _currentScene.currentImage.id; const markers = context.container().selectAll('.layer-panoramax .viewfield-group'); const sequences = context.container().selectAll('.layer-panoramax .sequence'); @@ -659,7 +658,7 @@ export default { //TODO: maybe this should be here (export?) function step(stepBy) { return function () { - if (!_activeImage) return; + if (!_currentScene.currentImage) return; let nextId; if(stepBy === 1) diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index ea0ab96cb..87b7f9a59 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -7,7 +7,8 @@ import {svgPath, svgPointTransform} from './helpers'; export function svgPanoramaxImages(projection, context, dispatch) { const throttledRedraw = _throttle(function () { dispatch.call('change'); }, 1000); - const minZoom = 15; + const imageMinZoom = 15; + const lineMinZoom = 10; const viewFieldZoomLevel = 18; let layer = d3_select(null); let _panoramax; @@ -26,6 +27,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { _panoramax = services.panoramax; _panoramax.event .on('viewerChanged', viewerChanged) + .on('loadedLines', throttledRedraw) .on('loadedImages', throttledRedraw); } else if (!services.panoramax && _panoramax) { _panoramax = null; @@ -269,6 +271,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { function drawImages(selection) { + const enabled = svgPanoramaxImages.enabled; const service = getService(); @@ -295,10 +298,22 @@ export function svgPanoramaxImages(projection, context, dispatch) { .merge(layer); if (enabled) { - if (service && ~~context.map().zoom() >= minZoom) { - editOn(); - update(); - service.loadImages(projection); + let zoom = ~~context.map().zoom(); + console.log(zoom) + if (service){ + if(zoom >= imageMinZoom) { + editOn(); + update(); + service.loadImages(projection); + } + else if(zoom >= lineMinZoom) { + editOn(); + update(); + service.loadLines(projection); + } + else { + editOff(); + } } else { editOff(); } @@ -306,6 +321,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { } + drawImages.enabled = function(_) { if (!arguments.length) return svgPanoramaxImages.enabled; svgPanoramaxImages.enabled = _; @@ -326,7 +342,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { }; drawImages.rendered = function(zoom) { - return zoom >= minZoom; + return zoom >= lineMinZoom; }; From e7daa508e81929610ce566303cf507e2fad514cd Mon Sep 17 00:00:00 2001 From: Mattia Pezzotti Date: Tue, 18 Jun 2024 13:09:28 +0200 Subject: [PATCH 18/44] refactored how panoramax shows frames --- css/60_photos.css | 25 +-- modules/services/panoramax.js | 261 ++++++-------------------------- modules/svg/panoramax_images.js | 11 +- 3 files changed, 51 insertions(+), 246 deletions(-) diff --git a/css/60_photos.css b/css/60_photos.css index 9f0893a53..67f75c549 100644 --- a/css/60_photos.css +++ b/css/60_photos.css @@ -327,27 +327,6 @@ padding:0 6px; pointer-events: initial; } -.ideditor .panoramax-wrapper { - position: relative; - background-color: #000; - background-image: url(img/loader-black.gif); - background-position: center; - background-repeat: no-repeat; -} -#ideditor-viewer-panoramax-simple-wrap { - height: 100%; -} -#ideditor-viewer-panoramax-simple { - width: 100%; - height: 100%; - transform-origin: 0 0; -} -#ideditor-viewer-panoramax-simple img { - width: 100%; - height: 100%; - object-fit: cover; - overflow: hidden -} label.panoramax-hd { float: left; @@ -392,7 +371,8 @@ label.panoramax-hd { } .ms-wrapper .pnlm-compass.pnlm-control, -.vegbilder-wrapper .pnlm-compass.pnlm-control { +.vegbilder-wrapper .pnlm-compass.pnlm-control, +.panoramax-wrapper .pnlm-compass.pnlm-control { width: 26px; height: 26px; left: 4px; @@ -494,6 +474,7 @@ label.streetside-hires { background-image: url(img/loader-black.gif); background-position: center; background-repeat: no-repeat; + object-fit: cover; } .photoviewer .plane-frame > img.plane-photo{ diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 960575716..621cf2779 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -1,7 +1,4 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; -import { select as d3_select } from 'd3-selection'; -import { timer as d3_timer } from 'd3-timer'; -import { zoom as d3_zoom, zoomIdentity as d3_zoomIdentity } from 'd3-zoom'; import Protobuf from 'pbf'; import RBush from 'rbush'; @@ -10,6 +7,8 @@ import { VectorTile } from '@mapbox/vector-tile'; import { utilRebind, utilTiler, utilQsString, utilStringQs, utilSetTransform, utilUniqueDomId} from '../util'; import { geoExtent, geoScaleToZoom } from '../geo'; import { t, localizer } from '../core/localizer'; +import pannellumPhotoFrame from './pannellum_photo'; +import planePhotoFrame from './plane_photo'; const apiUrl = 'https://panoramax.openstreetmap.fr/'; const tileUrl = apiUrl + 'api/map/{z}/{x}/{y}.pbf'; @@ -25,28 +24,16 @@ const sequenceLayer = 'sequences'; const minZoom = 10; const dispatch = d3_dispatch('loadedImages', 'loadedLines', 'viewerChanged'); -const imgZoom = d3_zoom() - .extent([[0, 0], [320, 240]]) - .translateExtent([[0, 0], [320, 240]]) - .scaleExtent([1, 15]); -const pannellumViewerCSS = 'pannellum/pannellum.css'; -const pannellumViewerJS = 'pannellum/pannellum.js'; -const resolution = 1080; let _cache; let _loadViewerPromise; let _pannellumViewer; let _definition = standardDefinition; let _isHD = false; -let _sceneOptions = { - showFullscreenCtrl: false, - autoLoad: true, - yaw: 0, - minHfov: 10, - maxHfov: 90, - hfov: 60, -}; -let _currScene = 0; + +let _planeFrame; +let _pannellumFrame; +let _currentFrame; let _currentScene = { currentImage : null, @@ -160,8 +147,9 @@ function loadTileDataToCache(data, tile) { acc_id: feature.properties.account_id, sequence_id: feature.properties.sequences.split("\"")[1], heading: feature.properties.heading, + image_path: "", resolution: feature.properties.resolution, - isPano: feature.properties.type === "equirectangular", + isPano: feature.properties.type == "equirectangular", model: feature.properties.model, }; cache.forImageId[d.id] = d; @@ -338,40 +326,28 @@ export default { } }, - initViewer: function () { - if (!window.pannellum) return; - if (_pannellumViewer) return; - - _currScene += 1; - const sceneID = _currScene.toString(); - const options = { - 'default': { firstScene: sceneID }, - scenes: {} - }; - options.scenes[sceneID] = _sceneOptions; - - _pannellumViewer = window.pannellum.viewer('ideditor-viewer-panoramax-pnlm', options); - }, - selectImage: function (context, id) { let that = this; - let d = this.cachedImage(id); - - this.setActiveImage(d); - - this.updateUrlImage(d.id); + let d = that.cachedImage(id); + that.setActiveImage(d); + that.updateUrlImage(d.id); let imageUrl = getImage(d.id, highDefinition); - let viewer = context.container().select('.photoviewer'); - if (!viewer.empty()) viewer.datum(d); + let viewer = context.container() + .select('.photoviewer'); + + if (!viewer.empty()) + viewer.datum(d); this.setStyles(context, null); if (!d) return this; - let wrap = context.container().select('.photoviewer .panoramax-wrapper'); + let wrap = context.container() + .select('.photoviewer .panoramax-wrapper'); + let attribution = wrap.selectAll('.photo-attribution').text(''); let line1 = attribution @@ -392,17 +368,8 @@ export default { .property('checked', _isHD) .on('click', (d3_event) => { d3_event.stopPropagation(); - _isHD = !_isHD; _definition = _isHD ? highDefinition : standardDefinition; - - let viewstate = { - yaw: _pannellumViewer.getYaw(), - pitch: _pannellumViewer.getPitch(), - hfov: _pannellumViewer.getHfov() - }; - - _sceneOptions = Object.assign(_sceneOptions, viewstate); that.selectImage(context, d.id) .showViewer(context); }); @@ -411,15 +378,6 @@ export default { .append('span') .call(t.append('panoramax.hd')); - wrap - .transition() - .duration(100) - .call(imgZoom.transform, d3_zoomIdentity); - - wrap - .selectAll('img') - .remove(); - if (d.capture_time) { attribution .append('span') @@ -449,6 +407,7 @@ export default { .text('panoramax.fr'); getImageData(d.sequence_id, d.id).then(function(data){ + _currentScene = { currentImage: null, nextImage: null, @@ -461,7 +420,8 @@ export default { _currentScene.nextImage = data.links[nextIndex]; if (prevIndex != -1) _currentScene.prevImage = data.links[prevIndex]; - _sceneOptions.panorama = _currentScene.currentImage.href; + + d.image_path = _currentScene.currentImage.href; wrap .selectAll('button.back') @@ -470,26 +430,12 @@ export default { .selectAll('button.forward') .classed('hide', _currentScene.nextImage == null); - if (d.isPano) { - _sceneOptions.type = "equirectangular"; - if (!_pannellumViewer) { - that.initViewer(); - } else { - _currScene += 1; - let sceneID = _currScene.toString(); - _pannellumViewer - .addScene(sceneID, _sceneOptions) - .loadScene(sceneID); + _currentFrame = d.isPano ? _pannellumFrame : _planeFrame; - if (_currScene > 2) { - sceneID = (_currScene - 1).toString(); - _pannellumViewer - .removeScene(sceneID); - } - } - } else { - that.initOnlyPhoto(context, imageUrl); - } + _currentFrame + .selectPhoto(d, true) + .showPhotoFrame(wrap); + }); function localeDateString(s) { @@ -503,35 +449,16 @@ export default { return this; }, - initOnlyPhoto: function(context, imageUrl) { - - if (_pannellumViewer) { - _pannellumViewer.destroy(); - _pannellumViewer = null; - } - - let wrap = context.container().select('#ideditor-viewer-panoramax-simple'); - - let imgWrap = wrap.select('img'); - - if (!imgWrap.empty()) { - imgWrap.attr('src', imageUrl); - } else { - wrap.append('img') - .attr('src', imageUrl); - } - - }, - - viewer: function() { - return _pannellumViewer; + photoFrame: function() { + return _currentFrame; }, ensureViewerLoaded: function(context) { let that = this; - let imgWrap = context.container().select('#ideditor-viewer-panoramax-simple > img'); + let imgWrap = context.container() + .select('#ideditor-viewer-panoramax-simple > img'); if (!imgWrap.empty()) { imgWrap.remove(); @@ -539,7 +466,9 @@ export default { if (_loadViewerPromise) return _loadViewerPromise; - let wrap = context.container().select('.photoviewer').selectAll('.panoramax-wrapper') + let wrap = context.container() + .select('.photoviewer') + .selectAll('.panoramax-wrapper') .data([0]); //TODO maybe all of this should be in panoramax_images? @@ -549,31 +478,9 @@ export default { .classed('hide', true) .on('dblclick.zoom', null); - let pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; - wrapEnter .append('div') - .attr('id', 'ideditor-viewer-panoramax-pnlm') - .on(pointerPrefix + 'down.panoramax', () => { - d3_select(window) - .on(pointerPrefix + 'move.panoramax', () => { - dispatch.call('viewerChanged'); - }, true); - }) - .on(pointerPrefix + 'up.panoramax pointercancel.panoramax', () => { - d3_select(window) - .on(pointerPrefix + 'move.panoramax', null); - - // continue dispatching events for a few seconds, in case viewer has inertia. - let t = d3_timer(elapsed => { - dispatch.call('viewerChanged'); - if (elapsed > 1000) { - t.stop(); - } - }); - }) - .append('div') - .attr('class', 'photo-attribution fillD'); + .attr('class', 'photo-attribution fillD'); const controlsEnter = wrapEnter .append('div') @@ -593,67 +500,16 @@ export default { .on('click.forward', step(1)) .text('►'); - wrapEnter - .append('div') - .attr('id', 'ideditor-viewer-panoramax-pnlm'); - - wrapEnter - .append('div') - .attr('id', 'ideditor-viewer-panoramax-simple-wrap') - .call(imgZoom.on('zoom', zoomPan)) - .append('div') - .attr('id', 'ideditor-viewer-panoramax-simple'); - - - // Register viewer resize handler - context.ui().photoviewer.on('resize.panoramax', () => { - if (_pannellumViewer) { - _pannellumViewer.resize(); - } - }); - - _loadViewerPromise = new Promise((resolve, reject) => { - let loadedCount = 0; - function loaded() { - loadedCount += 1; - - // wait until both files are loaded - if (loadedCount === 2) resolve(); - } - - const head = d3_select('head'); - - // load pannellum-viewercss - head.selectAll('#ideditor-panoramax-viewercss') - .data([0]) - .enter() - .append('link') - .attr('id', 'ideditor-panoramax-viewercss') - .attr('rel', 'stylesheet') - .attr('crossorigin', 'anonymous') - .attr('href', context.asset(pannellumViewerCSS)) - .on('load.servicePanoramax', loaded) - .on('error.servicePanoramax', function() { - reject(); - }); - - // load pannellum-viewerjs - head.selectAll('#ideditor-panoramax-viewerjs') - .data([0]) - .enter() - .append('script') - .attr('id', 'ideditor-panoramax-viewerjs') - .attr('crossorigin', 'anonymous') - .attr('src', context.asset(pannellumViewerJS)) - .on('load.servicePanoramax', loaded) - .on('error.servicePanoramax', function() { - reject(); - }); - }) - .catch(function() { - _loadViewerPromise = null; - }); + _loadViewerPromise = Promise.all([ + pannellumPhotoFrame.init(context, wrapEnter), + planePhotoFrame.init(context, wrapEnter) + ]).then(([pannellumPhotoFrame, planePhotoFrame]) => { + _pannellumFrame = pannellumPhotoFrame; + _pannellumFrame.event.on('viewerChanged', () => dispatch.call('viewerChanged')); + _planeFrame = planePhotoFrame; + _planeFrame.event.on('viewerChanged', () => dispatch.call('viewerChanged')); + }); //TODO: maybe this should be here (export?) function step(stepBy) { @@ -666,15 +522,7 @@ export default { else nextId = _currentScene.prevImage.id; if (!nextId) return; - - let viewstate = { - yaw: _pannellumViewer.getYaw(), - pitch: _pannellumViewer.getPitch(), - hfov: _pannellumViewer.getHfov() - }; - _sceneOptions = Object.assign(_sceneOptions, viewstate); - const nextImage = _cache.images.forImageId[nextId]; context.map().centerEase(nextImage.loc); @@ -683,56 +531,35 @@ export default { }; } - function zoomPan(d3_event) { - var t = d3_event.transform; - context.container().select('.photoviewer #ideditor-viewer-panoramax-simple') - .call(utilSetTransform, t.x, t.y, t.k); - } - return _loadViewerPromise; }, - yaw: function(yaw) { - if (typeof yaw !== 'number') return yaw; - _sceneOptions.yaw = yaw; - return this; - }, - showViewer: function (context) { let wrap = context.container().select('.photoviewer') .classed('hide', false); - let isHidden = wrap.selectAll('.photo-wrapper.panoramax-wrapper.hide').size(); - if (isHidden) { wrap .selectAll('.photo-wrapper:not(.panoramax-wrapper)') .classed('hide', true); - wrap .selectAll('.photo-wrapper.panoramax-wrapper') .classed('hide', false); } - return this; }, hideViewer: function (context) { let viewer = context.container().select('.photoviewer'); if (!viewer.empty()) viewer.datum(null); - this.updateUrlImage(null); - viewer .classed('hide', true) .selectAll('.photo-wrapper') .classed('hide', true); - context.container().selectAll('.viewfield-group, .sequence, .icon-sign') .classed('currentView', false); - this.setActiveImage(); - return this.setStyles(context, null); }, diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 87b7f9a59..70d852ec4 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -144,11 +144,10 @@ export function svgPanoramaxImages(projection, context, dispatch) { _selectedSequence = image.sequence_id; service - .ensureViewerLoaded(context, image.id) + .ensureViewerLoaded(context) .then(function() { service .selectImage(context, image.id) - .yaw(_viewerYaw) .showViewer(context); }); @@ -252,14 +251,13 @@ export function svgPanoramaxImages(projection, context, dispatch) { } function viewerChanged() { - var service = getService(); + const service = getService(); if (!service) return; - var viewer = service.viewer(); - if (!viewer) return; + const frame = service.photoFrame(); // update viewfield rotation - _viewerYaw = viewer.getYaw(); + _viewerYaw = frame.getYaw(); // avoid updating if the map is currently transformed // e.g. during drags or easing. @@ -299,7 +297,6 @@ export function svgPanoramaxImages(projection, context, dispatch) { if (enabled) { let zoom = ~~context.map().zoom(); - console.log(zoom) if (service){ if(zoom >= imageMinZoom) { editOn(); From 8b68f077996e794b359ae0bbd8f2e31bb9857e11 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Sat, 22 Jun 2024 10:11:44 +0200 Subject: [PATCH 19/44] added a way to show only sequences on low level zoom --- css/60_photos.css | 23 +++++------ modules/services/pannellum_photo.js | 2 +- modules/services/panoramax.js | 63 +++++++++++++++++------------ modules/svg/panoramax_images.js | 6 +-- 4 files changed, 53 insertions(+), 41 deletions(-) diff --git a/css/60_photos.css b/css/60_photos.css index 67f75c549..ea5692bdc 100644 --- a/css/60_photos.css +++ b/css/60_photos.css @@ -166,7 +166,7 @@ .sequence { fill: none; stroke-width: 2; - stroke-opacity: 0.4; + stroke-opacity: 0.6; } .sequence.highlighted, .sequence.currentView { @@ -301,7 +301,7 @@ width: 100%; height: 100%; object-fit: cover; - overflow: hidden + overflow: hidden; } /* panoramax Image Layer */ @@ -464,23 +464,22 @@ label.streetside-hires { .photo-wrapper { position: relative; background-color: #000; -} - -.photoviewer .plane-frame { - display: block; - overflow: hidden; - height: 100%; - width: 100%; background-image: url(img/loader-black.gif); background-position: center; background-repeat: no-repeat; - object-fit: cover; +} + +.photoviewer .plane-frame { + height: 100%; + width: 100%; + transform-origin: 0 0; } .photoviewer .plane-frame > img.plane-photo{ - width: auto; + width: 100%; height: 100%; - transform-origin: 0 0; + object-fit: cover; + overflow: hidden; } /* photo-controls (step forward, back, rotate) */ diff --git a/modules/services/pannellum_photo.js b/modules/services/pannellum_photo.js index 51ac406a8..bebc4de2c 100644 --- a/modules/services/pannellum_photo.js +++ b/modules/services/pannellum_photo.js @@ -119,7 +119,7 @@ export default { let newSceneOptions = { showFullscreenCtrl: false, autoLoad: false, - compass: true, + compass: false, yaw: 0, type: 'equirectangular', preview: data.preview_path, diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 621cf2779..919387205 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -23,6 +23,8 @@ const pictureLayer = 'pictures'; const sequenceLayer = 'sequences'; const minZoom = 10; +const imageMinZoom = 15; +const lineMinZoom = 10; const dispatch = d3_dispatch('loadedImages', 'loadedLines', 'viewerChanged'); let _cache; @@ -41,6 +43,8 @@ let _currentScene = { prevImage : null }; +let _activeImage; + // Partition viewport into higher zoom tiles function partitionViewport(projection) { @@ -129,6 +133,8 @@ function loadTileDataToCache(data, tile) { i, feature, loc, + locX, + locY, d; if (vectorTile.layers.hasOwnProperty(pictureLayer)) { @@ -144,7 +150,7 @@ function loadTileDataToCache(data, tile) { loc: loc, capture_time: feature.properties.ts, id: feature.properties.id, - acc_id: feature.properties.account_id, + account_id: feature.properties.account_id, sequence_id: feature.properties.sequences.split("\"")[1], heading: feature.properties.heading, image_path: "", @@ -179,7 +185,7 @@ function loadTileDataToCache(data, tile) { } // Quick access to image -function getImage(image_id, definition){ +function getImageURL(image_id, definition){ const requestUrl = imageBlobUrl.replace('{pictureID}', image_id) .replace('{definition}', definition); @@ -219,6 +225,7 @@ export default { }; _currentScene.currentImage = null; + _activeImage = null; }, // Get visible images @@ -234,16 +241,16 @@ export default { // Load images in the visible area loadImages: function(projection) { - loadTiles('images', tileUrl, 15, projection); + loadTiles('images', tileUrl, imageMinZoom, projection); }, // Load line in the visible area loadLines: function(projection) { - loadTiles('line', tileUrl, 10, projection); + loadTiles('line', tileUrl, lineMinZoom, projection); }, // Get visible sequences - sequences: function(projection) { + sequences: function(projection, zoom) { const viewport = projection.clipExtent(); const min = [viewport[0][0], viewport[1][1]]; const max = [viewport[1][0], viewport[0][1]]; @@ -251,40 +258,46 @@ export default { const sequenceIds = {}; let lineStrings = []; - _cache.images.rtree.search(bbox) - .forEach(function(d) { - if (d.data.sequence_id) { - sequenceIds[d.data.sequence_id] = true; - } - }); - - Object.keys(sequenceIds).forEach(function(sequenceId) { - if (_cache.sequences.lineString[sequenceId]) { + + if(zoom >= imageMinZoom){ + _cache.images.rtree.search(bbox).forEach(function(d) { + if (d.data.sequence_id) { + sequenceIds[d.data.sequence_id] = true; + } + }); + Object.keys(sequenceIds).forEach(function(sequenceId) { + if (_cache.sequences.lineString[sequenceId]) { + lineStrings = lineStrings.concat(_cache.sequences.lineString[sequenceId]); + } + }); + return lineStrings; + } + if(zoom >= lineMinZoom){ + Object.keys(_cache.sequences.lineString).forEach(function(sequenceId) { lineStrings = lineStrings.concat(_cache.sequences.lineString[sequenceId]); - } - }); - + }); + } return lineStrings; }, // Set the currently visible image setActiveImage: function(image) { if (image) { - _currentScene.currentImage = { + _activeImage = { id: image.id, sequence_id: image.sequence_id }; } else { - _currentScene.currentImage = null; + _activeImage = null; } }, // Update the currently highlighted sequence and selected bubble. setStyles: function(context, hovered) { - const hoveredImageId = hovered && hovered.id; + const hoveredImageId = hovered && hovered.id; const hoveredSequenceId = hovered && hovered.sequence_id; - const selectedSequenceId = _currentScene.currentImage && _currentScene.currentImage.sequence_id; - const selectedImageId = _currentScene.currentImage && _currentScene.currentImage.id; + const selectedSequenceId = _activeImage && _activeImage.sequence_id; + const selectedImageId = _activeImage && _activeImage.id; const markers = context.container().selectAll('.layer-panoramax .viewfield-group'); const sequences = context.container().selectAll('.layer-panoramax .sequence'); @@ -305,9 +318,9 @@ export default { function viewfieldPath() { let d = this.parentNode.__data__; if (d.isPano && d.id !== selectedImageId) { - return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; + return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; } else { - return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z'; + return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z'; } } @@ -333,7 +346,7 @@ export default { that.setActiveImage(d); that.updateUrlImage(d.id); - let imageUrl = getImage(d.id, highDefinition); + let imageUrl = getImageURL(d.id, highDefinition); let viewer = context.container() .select('.photoviewer'); diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 70d852ec4..39617ccb3 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -167,11 +167,11 @@ export function svgPanoramaxImages(projection, context, dispatch) { function update() { - const z = ~~context.map().zoom(); - const showViewfields = (z >= viewFieldZoomLevel); + const zoom = ~~context.map().zoom(); + const showViewfields = (zoom >= viewFieldZoomLevel); const service = getService(); - let sequences = (service ? service.sequences(projection) : []); + let sequences = (service ? service.sequences(projection, zoom) : []); let images = (service ? service.images(projection) : []); images = filterImages(images); From 44baa63ed5b936023942163a21b5f4b180e067f4 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Sun, 23 Jun 2024 14:05:06 +0200 Subject: [PATCH 20/44] username filter wip --- modules/renderer/photos.js | 2 +- modules/services/panoramax.js | 52 +++++++++++++++++++++++++++++---- modules/svg/panoramax_images.js | 23 +++++++++++++-- 3 files changed, 68 insertions(+), 9 deletions(-) diff --git a/modules/renderer/photos.js b/modules/renderer/photos.js index f2a8de21c..b28fd9b2f 100644 --- a/modules/renderer/photos.js +++ b/modules/renderer/photos.js @@ -128,7 +128,7 @@ export function rendererPhotos(context) { }; photos.shouldFilterByUsername = function() { - return !showsLayer('mapillary') && showsLayer('kartaview') && !showsLayer('streetside'); + return !showsLayer('mapillary') && showsLayer('kartaview') && !showsLayer('streetside') || showsLayer('panoramax'); }; photos.showsPhotoType = function(val) { diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 919387205..bf90444c5 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -14,6 +14,8 @@ const apiUrl = 'https://panoramax.openstreetmap.fr/'; const tileUrl = apiUrl + 'api/map/{z}/{x}/{y}.pbf'; const imageBlobUrl = apiUrl + 'api/pictures/{pictureID}/{definition}.jpg'; const imageDataUrl = apiUrl + 'api/collections/{collectionId}/items/{itemId}'; +const userIdUrl = apiUrl + 'api/users/search?q={username}' +const usernameURL = apiUrl + '/api/users/{userId}' const highDefinition = "hd"; const standardDefinition = "sd"; @@ -133,8 +135,6 @@ function loadTileDataToCache(data, tile) { i, feature, loc, - locX, - locY, d; if (vectorTile.layers.hasOwnProperty(pictureLayer)) { @@ -154,6 +154,7 @@ function loadTileDataToCache(data, tile) { sequence_id: feature.properties.sequences.split("\"")[1], heading: feature.properties.heading, image_path: "", + captured_by: "", resolution: feature.properties.resolution, isPano: feature.properties.type == "equirectangular", model: feature.properties.model, @@ -204,6 +205,28 @@ async function getImageData(collection_id, image_id){ return data; } +async function getUserId(username){ + const requestUrl = userIdUrl.replace('{username}', username); + + const response = await fetch(requestUrl, { method: 'GET' }); + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } + const data = await response.json(); + return data.features[0].id; +} + +async function getUsername(user_id){ + const requestUrl = usernameURL.replace('{userId}', user_id); + + const response = await fetch(requestUrl, { method: 'GET' }); + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } + const data = await response.json(); + return data.name; +} + export default { init: function() { if (!_cache) { @@ -339,6 +362,11 @@ export default { } }, + getUserIdFromName: async function(username){ + const id = await getUserId(username) + return id; + }, + selectImage: function (context, id) { let that = this; @@ -370,9 +398,9 @@ export default { const hdDomId = utilUniqueDomId('panoramax-hd'); let label = line1 - .append('label') - .attr('for', hdDomId) - .attr('class', 'panoramax-hd'); + .append('label') + .attr('for', hdDomId) + .attr('class', 'panoramax-hd'); label .append('input') @@ -458,6 +486,20 @@ export default { if (isNaN(d.getTime())) return null; return d.toLocaleDateString(localizer.localeCode(), options); } + + if (d.account_id) { + let line2 = attribution + .append('div') + .attr('class', 'attribution-row'); + + getUsername(d.account_id).then(function(username){ + line2 + .append('span') + .attr('class', 'captured_by') + .text('Captured by: ' + username); + d.captured_by = username; + }); + } return this; }, diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 39617ccb3..cc223175a 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -41,6 +41,9 @@ export function svgPanoramaxImages(projection, context, dispatch) { const showsFlat = context.photos().showsFlat(); const fromDate = context.photos().fromDate(); const toDate = context.photos().toDate(); + const username = context.photos().usernames(); + + const service = getService(); if (!showsPano || !showsFlat) { images = images.filter(function(image) { @@ -58,6 +61,14 @@ export function svgPanoramaxImages(projection, context, dispatch) { return new Date(image.capture_time).getTime() <= new Date(toDate).getTime(); }); } + if (username && service) { + service.getUserIdFromName(username).then(function(id){ + images = images.filter(function(image) { + return id == image.account_id ; + }); + }) + + } return images; } @@ -67,21 +78,27 @@ export function svgPanoramaxImages(projection, context, dispatch) { const showsFlat = context.photos().showsFlat(); const fromDate = context.photos().fromDate(); const toDate = context.photos().toDate(); + const usernames = context.photos().usernames(); if (!showsPano || !showsFlat) { sequences = sequences.filter(function(sequence) { - if (sequence.properties.type === "equirectangular") return showsPano; + if (sequence.type === "equirectangular") return showsPano; return showsFlat; }); } if (fromDate) { sequences = sequences.filter(function(sequence) { - return new Date(sequence.properties.date).getTime() >= new Date(fromDate).getTime().toString(); + return new Date(sequence.date).getTime() >= new Date(fromDate).getTime().toString(); }); } if (toDate) { sequences = sequences.filter(function(sequence) { - return new Date(sequence.properties.date).getTime() <= new Date(toDate).getTime().toString(); + return new Date(sequence.date).getTime() <= new Date(toDate).getTime().toString(); + }); + } + if (usernames) { + sequences = sequences.filter(function(sequence) { + return usernames.indexOf(sequence.account_id) !== -1; }); } From 6c74588ed8795cb631b3847776d32462d66b0c6c Mon Sep 17 00:00:00 2001 From: Mattia Pezzotti Date: Sun, 23 Jun 2024 16:18:18 +0200 Subject: [PATCH 21/44] added username filter --- modules/services/panoramax.js | 31 ++++++++++--------------- modules/svg/panoramax_images.js | 40 ++++++++++++++++----------------- 2 files changed, 32 insertions(+), 39 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index bf90444c5..d465adb99 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -15,7 +15,7 @@ const tileUrl = apiUrl + 'api/map/{z}/{x}/{y}.pbf'; const imageBlobUrl = apiUrl + 'api/pictures/{pictureID}/{definition}.jpg'; const imageDataUrl = apiUrl + 'api/collections/{collectionId}/items/{itemId}'; const userIdUrl = apiUrl + 'api/users/search?q={username}' -const usernameURL = apiUrl + '/api/users/{userId}' +const usernameURL = apiUrl + 'api/users/{userId}' const highDefinition = "hd"; const standardDefinition = "sd"; @@ -154,7 +154,6 @@ function loadTileDataToCache(data, tile) { sequence_id: feature.properties.sequences.split("\"")[1], heading: feature.properties.heading, image_path: "", - captured_by: "", resolution: feature.properties.resolution, isPano: feature.properties.type == "equirectangular", model: feature.properties.model, @@ -205,17 +204,6 @@ async function getImageData(collection_id, image_id){ return data; } -async function getUserId(username){ - const requestUrl = userIdUrl.replace('{username}', username); - - const response = await fetch(requestUrl, { method: 'GET' }); - if (!response.ok) { - throw new Error(response.status + ' ' + response.statusText); - } - const data = await response.json(); - return data.features[0].id; -} - async function getUsername(user_id){ const requestUrl = usernameURL.replace('{userId}', user_id); @@ -272,6 +260,17 @@ export default { loadTiles('line', tileUrl, lineMinZoom, projection); }, + getUserId: async function(username){ + const requestUrl = userIdUrl.replace('{username}', username); + + const response = await fetch(requestUrl, { method: 'GET' }); + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } + const data = await response.json(); + return data.features[0].id; + }, + // Get visible sequences sequences: function(projection, zoom) { const viewport = projection.clipExtent(); @@ -362,11 +361,6 @@ export default { } }, - getUserIdFromName: async function(username){ - const id = await getUserId(username) - return id; - }, - selectImage: function (context, id) { let that = this; @@ -497,7 +491,6 @@ export default { .append('span') .attr('class', 'captured_by') .text('Captured by: ' + username); - d.captured_by = username; }); } diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index cc223175a..14d0c5cf6 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -36,7 +36,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { return _panoramax; } - function filterImages(images) { + async function filterImages(images) { const showsPano = context.photos().showsPanoramic(); const showsFlat = context.photos().showsFlat(); const fromDate = context.photos().fromDate(); @@ -62,43 +62,46 @@ export function svgPanoramaxImages(projection, context, dispatch) { }); } if (username && service) { - service.getUserIdFromName(username).then(function(id){ - images = images.filter(function(image) { - return id == image.account_id ; - }); - }) - + let id = await service.getUserId(username); + + images = images.filter(function(image) { + return id == image.account_id; + }); } return images; } - function filterSequences(sequences) { + async function filterSequences(sequences) { const showsPano = context.photos().showsPanoramic(); const showsFlat = context.photos().showsFlat(); const fromDate = context.photos().fromDate(); const toDate = context.photos().toDate(); - const usernames = context.photos().usernames(); + const username = context.photos().usernames(); + + const service = getService(); if (!showsPano || !showsFlat) { sequences = sequences.filter(function(sequence) { - if (sequence.type === "equirectangular") return showsPano; + if (sequence.properties.type === "equirectangular") return showsPano; return showsFlat; }); } if (fromDate) { sequences = sequences.filter(function(sequence) { - return new Date(sequence.date).getTime() >= new Date(fromDate).getTime().toString(); + return new Date(sequence.properties.date).getTime() >= new Date(fromDate).getTime().toString(); }); } if (toDate) { sequences = sequences.filter(function(sequence) { - return new Date(sequence.date).getTime() <= new Date(toDate).getTime().toString(); + return new Date(sequence.properties.date).getTime() <= new Date(toDate).getTime().toString(); }); } - if (usernames) { + if (username && service) { + let id = await service.getUserId(username); + sequences = sequences.filter(function(sequence) { - return usernames.indexOf(sequence.account_id) !== -1; + return id == sequence.properties.account_id; }); } @@ -182,8 +185,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { if (service) service.setStyles(context, null); } - function update() { - + async function update() { const zoom = ~~context.map().zoom(); const showViewfields = (zoom >= viewFieldZoomLevel); @@ -191,8 +193,8 @@ export function svgPanoramaxImages(projection, context, dispatch) { let sequences = (service ? service.sequences(projection, zoom) : []); let images = (service ? service.images(projection) : []); - images = filterImages(images); - sequences = filterSequences(sequences, service); + images = await filterImages(images); + sequences = await filterSequences(sequences, service); let traces = layer.selectAll('.sequences').selectAll('.sequence') .data(sequences, function(d) { return d.id; }); @@ -334,8 +336,6 @@ export function svgPanoramaxImages(projection, context, dispatch) { } } - - drawImages.enabled = function(_) { if (!arguments.length) return svgPanoramaxImages.enabled; svgPanoramaxImages.enabled = _; From 6b0d6f7017d2e2c76b753efa67a13c69432b5b01 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Tue, 25 Jun 2024 14:58:48 +0200 Subject: [PATCH 22/44] added photo age filter --- data/core.yaml | 7 +++ modules/renderer/photos.js | 27 ++++++++- modules/ui/sections/photo_overlays.js | 81 +++++++++++++++++++++++++++ package-lock.json | 11 ++++ package.json | 1 + 5 files changed, 126 insertions(+), 1 deletion(-) diff --git a/data/core.yaml b/data/core.yaml index 806488a52..35787f935 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -886,6 +886,13 @@ en: username_filter: title: "Username" tooltip: "Show only photos by this user" + max_age_filter: + year: "Last year" + month: "Last month" + week: "Last week" + all: "Show all" + title: "Photo age" + tooltip: "Select maximum photo age" feature: points: description: Points diff --git a/modules/renderer/photos.js b/modules/renderer/photos.js index b28fd9b2f..cf26982b1 100644 --- a/modules/renderer/photos.js +++ b/modules/renderer/photos.js @@ -13,6 +13,7 @@ export function rendererPhotos(context) { var _dateFilters = ['fromDate', 'toDate']; var _fromDate; var _toDate; + var _maxPhotoAge; var _usernames; function photos() {} @@ -46,6 +47,10 @@ export function rendererPhotos(context) { return _dateFilters; }; + photos.maxPhotoAge = function() { + return _maxPhotoAge; + }; + photos.dateFilterValue = function(val) { return val === _dateFilters[0] ? _fromDate : _toDate; }; @@ -80,6 +85,22 @@ export function rendererPhotos(context) { } }; + photos.setMaxPhotoAge = function(maxPhotoAge){ + if(maxPhotoAge != -1){ + var fromDate = new Date(); + fromDate.setDate(fromDate.getDate() - maxPhotoAge); + var dd = String(fromDate.getDate()).padStart(2, '0'); + var mm = String(fromDate.getMonth() + 1).padStart(2, '0'); + var yyyy = fromDate.getFullYear(); + + fromDate = mm + '/' + dd + '/' + yyyy; + photos.setDateFilter('fromDate', fromDate) + _maxPhotoAge = maxPhotoAge; + } + else + photos.setDateFilter('fromDate', null) + } + photos.setUsernameFilter = function(val, updateUrl) { if (val && typeof val === 'string') val = val.replace(/;/g, ',').split(','); if (val) { @@ -119,9 +140,13 @@ export function rendererPhotos(context) { } photos.shouldFilterByDate = function() { - return showsLayer('mapillary') || showsLayer('kartaview') || showsLayer('streetside') || showsLayer('vegbilder') || showsLayer('panoramax'); + return showsLayer('mapillary') || showsLayer('kartaview') || showsLayer('streetside') || showsLayer('vegbilder'); }; + photos.shouldFilterByMaxAge = function(){ + return showsLayer('panoramax'); + } + photos.shouldFilterByPhotoType = function() { return showsLayer('mapillary') || (showsLayer('streetside') && showsLayer('kartaview')) || showsLayer('vegbilder') || showsLayer('panoramax'); diff --git a/modules/ui/sections/photo_overlays.js b/modules/ui/sections/photo_overlays.js index 7d5c35ea2..e9da0af76 100644 --- a/modules/ui/sections/photo_overlays.js +++ b/modules/ui/sections/photo_overlays.js @@ -30,6 +30,7 @@ export function uiSectionPhotoOverlays(context) { .merge(container) .call(drawPhotoItems) .call(drawPhotoTypeItems) + .call(drawMaxAgeFilter) .call(drawDateFilter) .call(drawUsernameFilter) .call(drawLocalPhotos); @@ -257,6 +258,86 @@ export function uiSectionPhotoOverlays(context) { .classed('active', filterEnabled); } + function drawMaxAgeFilter(selection){ + function filterEnabled(d) { + return context.photos().maxPhotoAge(d); + } + + var ul = selection + .selectAll('.layer-list-date-age') + .data([0]); + + ul.exit() + .remove(); + + ul = ul.enter() + .append('ul') + .attr('class', 'layer-list layer-list-date-age') + .merge(ul); + + var li = ul.selectAll('.list-item-date-age') + .data(context.photos().shouldFilterByMaxAge() ? ['max-age'] : []); + + li.exit() + .remove(); + + var liEnter = li.enter() + .append('li') + .attr('class', 'list-item-date-age'); + + var labelEnter = liEnter + .append('label') + .each(function() { + d3_select(this) + .call(uiTooltip() + .title(() => t.append('photo_overlays.max_age_filter.tooltip')) + .placement('top') + ); + }); + + labelEnter + .append('span') + .call(t.append('photo_overlays.max_age_filter.title')); + + labelEnter + .append('select') + .attr('type', 'text') + .attr('class', 'list-option') + .call(utilNoAuto) + + var select = labelEnter.selectAll('.list-option'); + + select + .append('option') + .attr('value', -1) + .call(t.append('photo_overlays.max_age_filter.all')); + + select + .append('option') + .attr('value', 7) + .call(t.append('photo_overlays.max_age_filter.week')); + + select + .append('option') + .attr('value', 31) + .call(t.append('photo_overlays.max_age_filter.month')); + + select + .append('option') + .attr('value', 365) + .call(t.append('photo_overlays.max_age_filter.year')); + + select + .on('change', function() { + var value = d3_select(this).property('value'); + context.photos().setMaxPhotoAge(parseInt(value)); + }); + + li + .merge(liEnter) + .classed('active', filterEnabled); + } + function drawUsernameFilter(selection) { function filterEnabled() { return context.photos().usernames(); diff --git a/package-lock.json b/package-lock.json index 1ac917a92..f61e49092 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "abortcontroller-polyfill": "^1.7.5", "aes-js": "^3.1.2", "alif-toolkit": "^1.2.9", + "all": "^0.0.0", "core-js-bundle": "^3.37.0", "diacritics": "1.3.0", "exifr": "^7.1.3", @@ -1902,6 +1903,11 @@ "version": "1.2.9", "license": "MIT" }, + "node_modules/all": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/all/-/all-0.0.0.tgz", + "integrity": "sha512-0oKlfNVv2d+d7c1gwjGspzgbwot47PGQ4b3v1ccx4mR8l9P/Y6E6Dr/yE8lNT63EcAKEbHo6UG3odDpC/NQcKw==" + }, "node_modules/amdefine": { "version": "1.0.1", "dev": true, @@ -10191,6 +10197,11 @@ "alif-toolkit": { "version": "1.2.9" }, + "all": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/all/-/all-0.0.0.tgz", + "integrity": "sha512-0oKlfNVv2d+d7c1gwjGspzgbwot47PGQ4b3v1ccx4mR8l9P/Y6E6Dr/yE8lNT63EcAKEbHo6UG3odDpC/NQcKw==" + }, "amdefine": { "version": "1.0.1", "dev": true diff --git a/package.json b/package.json index 1546a0f83..a2a5da8fb 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "abortcontroller-polyfill": "^1.7.5", "aes-js": "^3.1.2", "alif-toolkit": "^1.2.9", + "all": "^0.0.0", "core-js-bundle": "^3.37.0", "diacritics": "1.3.0", "exifr": "^7.1.3", From 21d99715a7ccbaeb7796f2ef0b118157fac4d76c Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Wed, 26 Jun 2024 13:12:26 +0200 Subject: [PATCH 23/44] fix zoom/pan glitch on plane photos When changing to a different photo, it can be the case that the new photo has a different aspect ratio: the zoom behaviour needs to be updated. Also, keeping the "orientation" does not make sense for plane photos, and even keeping the previously shown photo's zoom is also not typically desired for these. --- css/60_photos.css | 7 +++---- modules/services/plane_photo.js | 9 ++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/css/60_photos.css b/css/60_photos.css index ea5692bdc..1ba524771 100644 --- a/css/60_photos.css +++ b/css/60_photos.css @@ -475,11 +475,10 @@ label.streetside-hires { transform-origin: 0 0; } -.photoviewer .plane-frame > img.plane-photo{ - width: 100%; +.photoviewer .plane-frame > img.plane-photo { + width: auto; height: 100%; - object-fit: cover; - overflow: hidden; + transform-origin: 0 0; } /* photo-controls (step forward, back, rotate) */ diff --git a/modules/services/plane_photo.js b/modules/services/plane_photo.js index 980d01c22..938bf8750 100644 --- a/modules/services/plane_photo.js +++ b/modules/services/plane_photo.js @@ -87,14 +87,13 @@ export default { selectPhoto: function (data, keepOrientation) { dispatch.call('viewerChanged'); + loadImage(_photo, ''); loadImage(_photo, data.image_path) .then(() => { - if (!keepOrientation) { - imgZoom = zoomBeahvior(); - _wrapper.call(imgZoom); - _wrapper.call(imgZoom.transform, d3_zoomIdentity.translate(-_widthOverflow / 2, 0)); - } + imgZoom = zoomBeahvior(); + _wrapper.call(imgZoom); + _wrapper.call(imgZoom.transform, d3_zoomIdentity.translate(-_widthOverflow / 2, 0)); }); return this; }, From bdcebcab3325bebd25c22eb268c98e15a89f5e13 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Fri, 28 Jun 2024 17:54:37 +0200 Subject: [PATCH 24/44] date slider filter (kinda finished just buggy label) --- css/60_photos.css | 5 ++ data/core.yaml | 3 ++ modules/renderer/photos.js | 22 ++++++++ modules/services/panoramax.js | 22 ++++++++ modules/svg/panoramax_images.js | 29 ++++++++++- modules/ui/sections/photo_overlays.js | 74 ++++++++++++++++++++++++++- 6 files changed, 153 insertions(+), 2 deletions(-) diff --git a/css/60_photos.css b/css/60_photos.css index ea5692bdc..4984abf3f 100644 --- a/css/60_photos.css +++ b/css/60_photos.css @@ -621,3 +621,8 @@ label.streetside-hires { border-radius: 4px; cursor: pointer; } + +datalist { + display: inline-block; + vertical-align: middle; + } \ No newline at end of file diff --git a/data/core.yaml b/data/core.yaml index 35787f935..2035f27aa 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -893,6 +893,9 @@ en: all: "Show all" title: "Photo age" tooltip: "Select maximum photo age" + age_slider_filter: + title: "Year Slider" + tooltip: "Select oldest photo year" feature: points: description: Points diff --git a/modules/renderer/photos.js b/modules/renderer/photos.js index cf26982b1..3b0646b4b 100644 --- a/modules/renderer/photos.js +++ b/modules/renderer/photos.js @@ -14,6 +14,7 @@ export function rendererPhotos(context) { var _fromDate; var _toDate; var _maxPhotoAge; + var _maxPhotoYear; var _usernames; function photos() {} @@ -51,6 +52,10 @@ export function rendererPhotos(context) { return _maxPhotoAge; }; + photos.maxPhotoYear = function() { + return _maxPhotoYear; + }; + photos.dateFilterValue = function(val) { return val === _dateFilters[0] ? _fromDate : _toDate; }; @@ -101,6 +106,19 @@ export function rendererPhotos(context) { photos.setDateFilter('fromDate', null) } + photos.setMaxPhotoYear = function(maxPhotoYear){ + if(maxPhotoYear != null){ + var fromDate = new Date(); + fromDate = '01/01/' + maxPhotoYear; + photos.setDateFilter('fromDate', fromDate); + _maxPhotoYear = maxPhotoYear; + } + else{ + photos.setDateFilter('fromDate', null); + } + } + + photos.setUsernameFilter = function(val, updateUrl) { if (val && typeof val === 'string') val = val.replace(/;/g, ',').split(','); if (val) { @@ -144,6 +162,10 @@ export function rendererPhotos(context) { }; photos.shouldFilterByMaxAge = function(){ + return false; + } + + photos.shouldFilterDateBySlider = function(){ return showsLayer('panoramax'); } diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index d465adb99..b68b47507 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -39,6 +39,8 @@ let _planeFrame; let _pannellumFrame; let _currentFrame; +let _oldestDate; + let _currentScene = { currentImage : null, nextImage : null, @@ -162,6 +164,14 @@ function loadTileDataToCache(data, tile) { features.push({ minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d }); + + if(_oldestDate){ + if(d.capture_time < _oldestDate) + _oldestDate = d.capture_time; + } + else{ + _oldestDate = d.capture_time; + } } if (cache.rtree) { cache.rtree.load(features); @@ -179,6 +189,13 @@ function loadTileDataToCache(data, tile) { } else { cache.lineString[feature.properties.id] = [feature]; } + if(_oldestDate){ + if(feature.properties.date < _oldestDate) + _oldestDate = feature.properties.date; + } + else{ + _oldestDate = feature.properties.date; + } } } @@ -271,6 +288,11 @@ export default { return data.features[0].id; }, + getOldestDate: function(){ + if(_oldestDate) + return _oldestDate.substr(0, 4); + }, + // Get visible sequences sequences: function(projection, zoom) { const viewport = projection.clipExtent(); diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 14d0c5cf6..e5ccdff50 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -185,6 +185,31 @@ export function svgPanoramaxImages(projection, context, dispatch) { if (service) service.setStyles(context, null); } + function updateSlider(oldestDate){ + let currYear = new Date(); + let slider = d3_select('.list-option-date-slider'); + + let label = slider.select(function() { return this.parentNode; }) + + label.selectAll('datalist').remove(); + + let datalist = label.append('datalist').attr('id', 'dateValues'); + + datalist + .append('option') + .attr('value', currYear.getFullYear()) + .attr('label', currYear.getFullYear()) + + if(oldestDate){ + slider.attr('min', oldestDate); + + datalist + .append('option') + .attr('value', oldestDate) + .attr('label', oldestDate) + } + } + async function update() { const zoom = ~~context.map().zoom(); const showViewfields = (zoom >= viewFieldZoomLevel); @@ -192,6 +217,9 @@ export function svgPanoramaxImages(projection, context, dispatch) { const service = getService(); let sequences = (service ? service.sequences(projection, zoom) : []); let images = (service ? service.images(projection) : []); + let oldestDate = service.getOldestDate(); + + updateSlider(oldestDate); images = await filterImages(images); sequences = await filterSequences(sequences, service); @@ -266,7 +294,6 @@ export function svgPanoramaxImages(projection, context, dispatch) { return 'M 6,9 C 8,8.4 8,8.4 10,9 L 16,-2 C 12,-5 4,-5 0,-2 z'; } } - } function viewerChanged() { diff --git a/modules/ui/sections/photo_overlays.js b/modules/ui/sections/photo_overlays.js index e9da0af76..cbb9bfc87 100644 --- a/modules/ui/sections/photo_overlays.js +++ b/modules/ui/sections/photo_overlays.js @@ -30,7 +30,7 @@ export function uiSectionPhotoOverlays(context) { .merge(container) .call(drawPhotoItems) .call(drawPhotoTypeItems) - .call(drawMaxAgeFilter) + .call(drawDateSlider) .call(drawDateFilter) .call(drawUsernameFilter) .call(drawLocalPhotos); @@ -338,6 +338,78 @@ export function uiSectionPhotoOverlays(context) { .classed('active', filterEnabled); } + function drawDateSlider(selection){ + + function filterEnabled(d) { + return context.photos().maxPhotoYear(d); + } + + var currYear = new Date(); + currYear = currYear.getFullYear(); + + var ul = selection + .selectAll('.layer-list-date-slider') + .data([0]); + + ul.exit() + .remove(); + + ul = ul.enter() + .append('ul') + .attr('class', 'layer-list layer-list-date-slider') + .merge(ul); + + var li = ul.selectAll('.list-item-date-slider') + .data(context.photos().shouldFilterDateBySlider() ? ['date-slider'] : []); + + li.exit() + .remove(); + + var liEnter = li.enter() + .append('li') + .attr('class', 'list-item-date-slider'); + + var labelEnter = liEnter + .append('label') + .each(function() { + d3_select(this) + .call(uiTooltip() + .title(() => t.append('photo_overlays.age_slider_filter.tooltip')) + .placement('top') + ); + }); + + labelEnter + .append('span') + .call(t.append('photo_overlays.age_slider_filter.title')); + + labelEnter + .append('input') + .attr('type', 'range') + .attr('max', currYear) + .attr('min', currYear) + .attr('list', 'dateValues') + .attr('class', 'list-option-date-slider') + .call(utilNoAuto) + .on('change', function() { + var value = d3_select(this).property('value'); + context.photos().setMaxPhotoYear(parseInt(value)); + }); + + let datalist = labelEnter + .append('datalist') + .attr('id', 'dateValues') + + datalist + .append('option') + .attr('value', currYear) + .attr('label', currYear) + + li + .merge(liEnter) + .classed('active', filterEnabled); + } + function drawUsernameFilter(selection) { function filterEnabled() { return context.photos().usernames(); From fd7534b175fefad5d6b178356e208bc59c80eebc Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Sat, 29 Jun 2024 09:11:43 +0200 Subject: [PATCH 25/44] fixed age slider label, added current selected year label and maximum oldest year --- css/60_photos.css | 12 +++++++---- modules/svg/panoramax_images.js | 29 ++++++++++++++++++--------- modules/ui/sections/photo_overlays.js | 15 ++++++++++++-- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/css/60_photos.css b/css/60_photos.css index 87b00fa2c..c644eada5 100644 --- a/css/60_photos.css +++ b/css/60_photos.css @@ -621,7 +621,11 @@ label.streetside-hires { cursor: pointer; } -datalist { - display: inline-block; - vertical-align: middle; - } \ No newline at end of file +.slider-wrap { + display: inline-block; +} + +.year-datalist { + display: flex; + justify-content: space-between; +} \ No newline at end of file diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index e5ccdff50..490bdae14 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -186,21 +186,19 @@ export function svgPanoramaxImages(projection, context, dispatch) { } function updateSlider(oldestDate){ + let maxOldestYear = 2010; let currYear = new Date(); let slider = d3_select('.list-option-date-slider'); - let label = slider.select(function() { return this.parentNode; }) + let sliderWrap = slider.select(function() { return this.parentNode; }) - label.selectAll('datalist').remove(); + sliderWrap.selectAll('datalist').remove(); - let datalist = label.append('datalist').attr('id', 'dateValues'); + let datalist = sliderWrap.append('datalist') + .attr('id', 'dateValues') + .attr('class', 'year-datalist'); - datalist - .append('option') - .attr('value', currYear.getFullYear()) - .attr('label', currYear.getFullYear()) - - if(oldestDate){ + if(oldestDate > maxOldestYear){ slider.attr('min', oldestDate); datalist @@ -208,6 +206,19 @@ export function svgPanoramaxImages(projection, context, dispatch) { .attr('value', oldestDate) .attr('label', oldestDate) } + else{ + slider.attr('min', maxOldestYear); + + datalist + .append('option') + .attr('value', maxOldestYear) + .attr('label', maxOldestYear) + } + + datalist + .append('option') + .attr('value', currYear.getFullYear()) + .attr('label', currYear.getFullYear()) } async function update() { diff --git a/modules/ui/sections/photo_overlays.js b/modules/ui/sections/photo_overlays.js index cbb9bfc87..531490226 100644 --- a/modules/ui/sections/photo_overlays.js +++ b/modules/ui/sections/photo_overlays.js @@ -383,7 +383,16 @@ export function uiSectionPhotoOverlays(context) { .append('span') .call(t.append('photo_overlays.age_slider_filter.title')); - labelEnter + let sliderWrap = labelEnter + .append('div') + .attr('class','slider-wrap') + + let output = sliderWrap + .append('output') + .html(currYear) + .attr('class','year-selected'); + + sliderWrap .append('input') .attr('type', 'range') .attr('max', currYear) @@ -394,10 +403,12 @@ export function uiSectionPhotoOverlays(context) { .on('change', function() { var value = d3_select(this).property('value'); context.photos().setMaxPhotoYear(parseInt(value)); + output.html(value); }); - let datalist = labelEnter + let datalist = sliderWrap .append('datalist') + .attr('class', 'year-datalist') .attr('id', 'dateValues') datalist From b1f787c94da256d6f59a728f1363a8439afc7d76 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Sat, 6 Jul 2024 13:38:29 +0200 Subject: [PATCH 26/44] fixed typos and syntax errors --- css/60_photos.css | 21 +++--- modules/renderer/photos.js | 43 ++++++------- modules/services/panoramax.js | 92 +++++++++++++-------------- modules/services/plane_photo.js | 2 +- modules/svg/panoramax_images.js | 45 ++++++------- modules/ui/sections/photo_overlays.js | 26 ++++---- 6 files changed, 112 insertions(+), 117 deletions(-) diff --git a/css/60_photos.css b/css/60_photos.css index c644eada5..76612928b 100644 --- a/css/60_photos.css +++ b/css/60_photos.css @@ -341,6 +341,19 @@ label.panoramax-hd { margin: 0 5px; } +.slider-wrap { + display: inline-block; +} + +.year-datalist { + display: flex; + justify-content: space-between; +} + +.list-option-date-slider{ + direction: rtl +} + /* Streetside Viewer (pannellum) */ .ms-wrapper .photo-attribution .image-link { @@ -621,11 +634,3 @@ label.streetside-hires { cursor: pointer; } -.slider-wrap { - display: inline-block; -} - -.year-datalist { - display: flex; - justify-content: space-between; -} \ No newline at end of file diff --git a/modules/renderer/photos.js b/modules/renderer/photos.js index 3b0646b4b..0fdae6a4b 100644 --- a/modules/renderer/photos.js +++ b/modules/renderer/photos.js @@ -13,7 +13,7 @@ export function rendererPhotos(context) { var _dateFilters = ['fromDate', 'toDate']; var _fromDate; var _toDate; - var _maxPhotoAge; + var _maxPhotoDate; var _maxPhotoYear; var _usernames; @@ -49,7 +49,7 @@ export function rendererPhotos(context) { }; photos.maxPhotoAge = function() { - return _maxPhotoAge; + return _maxPhotoDate; }; photos.maxPhotoYear = function() { @@ -90,33 +90,32 @@ export function rendererPhotos(context) { } }; - photos.setMaxPhotoAge = function(maxPhotoAge){ - if(maxPhotoAge != -1){ + photos.setFromDateFilter = function(date){ + if (date !== -1){ var fromDate = new Date(); - fromDate.setDate(fromDate.getDate() - maxPhotoAge); + fromDate.setDate(fromDate.getDate() - date); var dd = String(fromDate.getDate()).padStart(2, '0'); var mm = String(fromDate.getMonth() + 1).padStart(2, '0'); var yyyy = fromDate.getFullYear(); fromDate = mm + '/' + dd + '/' + yyyy; - photos.setDateFilter('fromDate', fromDate) - _maxPhotoAge = maxPhotoAge; - } - else - photos.setDateFilter('fromDate', null) - } - - photos.setMaxPhotoYear = function(maxPhotoYear){ - if(maxPhotoYear != null){ - var fromDate = new Date(); - fromDate = '01/01/' + maxPhotoYear; photos.setDateFilter('fromDate', fromDate); - _maxPhotoYear = maxPhotoYear; - } - else{ + _maxPhotoDate = date; + } else { photos.setDateFilter('fromDate', null); } - } + }; + + photos.setFromYearFilter = function(year){ + if (year !== null){ + var fromDate = new Date(); + fromDate = '01/01/' + year; + photos.setDateFilter('fromDate', fromDate); + _maxPhotoYear = year; + } else { + photos.setDateFilter('fromDate', null); + } + }; photos.setUsernameFilter = function(val, updateUrl) { @@ -163,11 +162,11 @@ export function rendererPhotos(context) { photos.shouldFilterByMaxAge = function(){ return false; - } + }; photos.shouldFilterDateBySlider = function(){ return showsLayer('panoramax'); - } + }; photos.shouldFilterByPhotoType = function() { return showsLayer('mapillary') || diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index b68b47507..ca99f8d2f 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -4,22 +4,21 @@ import Protobuf from 'pbf'; import RBush from 'rbush'; import { VectorTile } from '@mapbox/vector-tile'; -import { utilRebind, utilTiler, utilQsString, utilStringQs, utilSetTransform, utilUniqueDomId} from '../util'; +import { utilRebind, utilTiler, utilQsString, utilStringQs, utilUniqueDomId} from '../util'; import { geoExtent, geoScaleToZoom } from '../geo'; import { t, localizer } from '../core/localizer'; import pannellumPhotoFrame from './pannellum_photo'; import planePhotoFrame from './plane_photo'; const apiUrl = 'https://panoramax.openstreetmap.fr/'; -const tileUrl = apiUrl + 'api/map/{z}/{x}/{y}.pbf'; +const tileUrl = apiUrl + 'api/map/{z}/{x}/{y}.mvt'; const imageBlobUrl = apiUrl + 'api/pictures/{pictureID}/{definition}.jpg'; const imageDataUrl = apiUrl + 'api/collections/{collectionId}/items/{itemId}'; -const userIdUrl = apiUrl + 'api/users/search?q={username}' -const usernameURL = apiUrl + 'api/users/{userId}' +const userIdUrl = apiUrl + 'api/users/search?q={username}'; +const usernameURL = apiUrl + 'api/users/{userId}'; -const highDefinition = "hd"; -const standardDefinition = "sd"; -const thumbnailDefinition = "thumb"; +const highDefinition = 'hd'; +const standardDefinition = 'sd'; const pictureLayer = 'pictures'; const sequenceLayer = 'sequences'; @@ -31,7 +30,6 @@ const dispatch = d3_dispatch('loadedImages', 'loadedLines', 'viewerChanged'); let _cache; let _loadViewerPromise; -let _pannellumViewer; let _definition = standardDefinition; let _isHD = false; @@ -153,23 +151,23 @@ function loadTileDataToCache(data, tile) { capture_time: feature.properties.ts, id: feature.properties.id, account_id: feature.properties.account_id, - sequence_id: feature.properties.sequences.split("\"")[1], + sequence_id: feature.properties.sequences.split('\"')[1], heading: feature.properties.heading, - image_path: "", + image_path: '', resolution: feature.properties.resolution, - isPano: feature.properties.type == "equirectangular", + isPano: feature.properties.type === 'equirectangular', model: feature.properties.model, }; cache.forImageId[d.id] = d; features.push({ minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1], data: d }); - - if(_oldestDate){ - if(d.capture_time < _oldestDate) + + if (_oldestDate){ + if (d.capture_time < _oldestDate){ _oldestDate = d.capture_time; - } - else{ + } + } else { _oldestDate = d.capture_time; } } @@ -189,16 +187,15 @@ function loadTileDataToCache(data, tile) { } else { cache.lineString[feature.properties.id] = [feature]; } - if(_oldestDate){ - if(feature.properties.date < _oldestDate) + if (_oldestDate){ + if (feature.properties.date < _oldestDate){ _oldestDate = feature.properties.date; - } - else{ + } + } else { _oldestDate = feature.properties.date; } } } - } // Quick access to image @@ -211,7 +208,7 @@ function getImageURL(image_id, definition){ async function getImageData(collection_id, image_id){ const requestUrl = imageDataUrl.replace('{collectionId}', collection_id) - .replace('{itemId}', image_id) + .replace('{itemId}', image_id); const response = await fetch(requestUrl, { method: 'GET' }); if (!response.ok) { @@ -266,7 +263,6 @@ export default { return _cache.images.forImageId[imageKey]; }, - // Load images in the visible area loadImages: function(projection) { loadTiles('images', tileUrl, imageMinZoom, projection); @@ -279,7 +275,7 @@ export default { getUserId: async function(username){ const requestUrl = userIdUrl.replace('{username}', username); - + const response = await fetch(requestUrl, { method: 'GET' }); if (!response.ok) { throw new Error(response.status + ' ' + response.statusText); @@ -289,8 +285,9 @@ export default { }, getOldestDate: function(){ - if(_oldestDate) + if (_oldestDate){ return _oldestDate.substr(0, 4); + } }, // Get visible sequences @@ -302,8 +299,7 @@ export default { const sequenceIds = {}; let lineStrings = []; - - if(zoom >= imageMinZoom){ + if (zoom >= imageMinZoom){ _cache.images.rtree.search(bbox).forEach(function(d) { if (d.data.sequence_id) { sequenceIds[d.data.sequence_id] = true; @@ -316,11 +312,11 @@ export default { }); return lineStrings; } - if(zoom >= lineMinZoom){ + if (zoom >= lineMinZoom){ Object.keys(_cache.sequences.lineString).forEach(function(sequenceId) { lineStrings = lineStrings.concat(_cache.sequences.lineString[sequenceId]); }); - } + } return lineStrings; }, @@ -395,8 +391,7 @@ export default { let viewer = context.container() .select('.photoviewer'); - if (!viewer.empty()) - viewer.datum(d); + if (!viewer.empty()) viewer.datum(d); this.setStyles(context, null); @@ -404,7 +399,7 @@ export default { let wrap = context.container() .select('.photoviewer .panoramax-wrapper'); - + let attribution = wrap.selectAll('.photo-attribution').text(''); let line1 = attribution @@ -449,7 +444,7 @@ export default { attribution .append('a') .attr('class', 'report-photo') - .attr('href', "mailto:signalement.ign@panoramax.fr") + .attr('href', 'mailto:signalement.ign@panoramax.fr') .call(t.append('panoramax.report')); attribution @@ -464,35 +459,37 @@ export default { .text('panoramax.fr'); getImageData(d.sequence_id, d.id).then(function(data){ - _currentScene = { currentImage: null, nextImage: null, prevImage: null }; - _currentScene.currentImage = data["assets"][_definition]; - const nextIndex = data.links.findIndex(x => x.rel == "next"); - const prevIndex = data.links.findIndex(x => x.rel == "prev"); - if (nextIndex != -1) + _currentScene.currentImage = data.assets[_definition]; + const nextIndex = data.links.findIndex(x => x.rel === 'next'); + const prevIndex = data.links.findIndex(x => x.rel === 'prev'); + + if (nextIndex !== -1){ _currentScene.nextImage = data.links[nextIndex]; - if (prevIndex != -1) + } + if (prevIndex !== -1){ _currentScene.prevImage = data.links[prevIndex]; + } d.image_path = _currentScene.currentImage.href; wrap .selectAll('button.back') - .classed('hide', _currentScene.prevImage == null); + .classed('hide', _currentScene.prevImage === null); wrap .selectAll('button.forward') - .classed('hide', _currentScene.nextImage == null); - + .classed('hide', _currentScene.nextImage === null); + _currentFrame = d.isPano ? _pannellumFrame : _planeFrame; _currentFrame .selectPhoto(d, true) .showPhotoFrame(wrap); - + }); function localeDateString(s) { @@ -502,7 +499,7 @@ export default { if (isNaN(d.getTime())) return null; return d.toLocaleDateString(localizer.localeCode(), options); } - + if (d.account_id) { let line2 = attribution .append('div') @@ -541,7 +538,6 @@ export default { .selectAll('.panoramax-wrapper') .data([0]); - //TODO maybe all of this should be in panoramax_images? let wrapEnter = wrap.enter() .append('div') .attr('class', 'photo-wrapper panoramax-wrapper') @@ -581,18 +577,16 @@ export default { _planeFrame.event.on('viewerChanged', () => dispatch.call('viewerChanged')); }); - //TODO: maybe this should be here (export?) function step(stepBy) { return function () { if (!_currentScene.currentImage) return; let nextId; - if(stepBy === 1) - nextId = _currentScene.nextImage.id; + if (stepBy === 1) nextId = _currentScene.nextImage.id; else nextId = _currentScene.prevImage.id; if (!nextId) return; - + const nextImage = _cache.images.forImageId[nextId]; context.map().centerEase(nextImage.loc); diff --git a/modules/services/plane_photo.js b/modules/services/plane_photo.js index 938bf8750..4261ed085 100644 --- a/modules/services/plane_photo.js +++ b/modules/services/plane_photo.js @@ -85,7 +85,7 @@ export default { return this; }, - selectPhoto: function (data, keepOrientation) { + selectPhoto: function (data) { dispatch.call('viewerChanged'); loadImage(_photo, ''); diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 490bdae14..7c311c007 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -10,11 +10,12 @@ export function svgPanoramaxImages(projection, context, dispatch) { const imageMinZoom = 15; const lineMinZoom = 10; const viewFieldZoomLevel = 18; + const maxOldestYear = 2010; let layer = d3_select(null); let _panoramax; let _viewerYaw = 0; let _selectedSequence; - + function init() { if (svgPanoramaxImages.initialized) return; svgPanoramaxImages.enabled = false; @@ -65,8 +66,8 @@ export function svgPanoramaxImages(projection, context, dispatch) { let id = await service.getUserId(username); images = images.filter(function(image) { - return id == image.account_id; - }); + return id === image.account_id; + }); } return images; @@ -83,7 +84,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { if (!showsPano || !showsFlat) { sequences = sequences.filter(function(sequence) { - if (sequence.properties.type === "equirectangular") return showsPano; + if (sequence.properties.type === 'equirectangular') return showsPano; return showsFlat; }); } @@ -101,7 +102,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { let id = await service.getUserId(username); sequences = sequences.filter(function(sequence) { - return id == sequence.properties.account_id; + return id === sequence.properties.account_id; }); } @@ -185,12 +186,11 @@ export function svgPanoramaxImages(projection, context, dispatch) { if (service) service.setStyles(context, null); } - function updateSlider(oldestDate){ - let maxOldestYear = 2010; + function updateYearSlider(oldestDate){ let currYear = new Date(); let slider = d3_select('.list-option-date-slider'); - let sliderWrap = slider.select(function() { return this.parentNode; }) + let sliderWrap = slider.select(function() { return this.parentNode; }); sliderWrap.selectAll('datalist').remove(); @@ -198,28 +198,27 @@ export function svgPanoramaxImages(projection, context, dispatch) { .attr('id', 'dateValues') .attr('class', 'year-datalist'); - if(oldestDate > maxOldestYear){ + if (oldestDate > maxOldestYear){ slider.attr('min', oldestDate); datalist .append('option') .attr('value', oldestDate) - .attr('label', oldestDate) - } - else{ + .attr('label', oldestDate); + } else { slider.attr('min', maxOldestYear); datalist .append('option') .attr('value', maxOldestYear) - .attr('label', maxOldestYear) - } + .attr('label', maxOldestYear); + }; datalist .append('option') .attr('value', currYear.getFullYear()) - .attr('label', currYear.getFullYear()) - } + .attr('label', currYear.getFullYear()); + }; async function update() { const zoom = ~~context.map().zoom(); @@ -229,8 +228,8 @@ export function svgPanoramaxImages(projection, context, dispatch) { let sequences = (service ? service.sequences(projection, zoom) : []); let images = (service ? service.images(projection) : []); let oldestDate = service.getOldestDate(); - - updateSlider(oldestDate); + + updateYearSlider(oldestDate); images = await filterImages(images); sequences = await filterSequences(sequences, service); @@ -326,7 +325,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { function drawImages(selection) { - + const enabled = svgPanoramaxImages.enabled; const service = getService(); @@ -355,17 +354,15 @@ export function svgPanoramaxImages(projection, context, dispatch) { if (enabled) { let zoom = ~~context.map().zoom(); if (service){ - if(zoom >= imageMinZoom) { + if (zoom >= imageMinZoom) { editOn(); update(); service.loadImages(projection); - } - else if(zoom >= lineMinZoom) { + } else if (zoom >= lineMinZoom) { editOn(); update(); service.loadLines(projection); - } - else { + } else { editOff(); } } else { diff --git a/modules/ui/sections/photo_overlays.js b/modules/ui/sections/photo_overlays.js index 531490226..ee892015b 100644 --- a/modules/ui/sections/photo_overlays.js +++ b/modules/ui/sections/photo_overlays.js @@ -303,8 +303,8 @@ export function uiSectionPhotoOverlays(context) { .append('select') .attr('type', 'text') .attr('class', 'list-option') - .call(utilNoAuto) - + .call(utilNoAuto); + var select = labelEnter.selectAll('.list-option'); select @@ -330,7 +330,7 @@ export function uiSectionPhotoOverlays(context) { select .on('change', function() { var value = d3_select(this).property('value'); - context.photos().setMaxPhotoAge(parseInt(value)); + context.photos().setMaxPhotoAge(parseInt(value, 10)); }); li @@ -345,7 +345,7 @@ export function uiSectionPhotoOverlays(context) { } var currYear = new Date(); - currYear = currYear.getFullYear(); + currYear = parseInt(currYear.getFullYear(), 10); var ul = selection .selectAll('.layer-list-date-slider') @@ -385,36 +385,36 @@ export function uiSectionPhotoOverlays(context) { let sliderWrap = labelEnter .append('div') - .attr('class','slider-wrap') + .attr('class','slider-wrap'); let output = sliderWrap .append('output') - .html(currYear) .attr('class','year-selected'); sliderWrap .append('input') .attr('type', 'range') .attr('max', currYear) - .attr('min', currYear) .attr('list', 'dateValues') .attr('class', 'list-option-date-slider') .call(utilNoAuto) .on('change', function() { - var value = d3_select(this).property('value'); - context.photos().setMaxPhotoYear(parseInt(value)); - output.html(value); + let value = parseInt(d3_select(this).property('value'), 10); + let minYear = parseInt(d3_select(this).property('min'), 10); + value = minYear + (currYear - value); + context.photos().setFromYearFilter(value); + output.html(value + ' - ' + currYear); }); let datalist = sliderWrap .append('datalist') .attr('class', 'year-datalist') - .attr('id', 'dateValues') - + .attr('id', 'dateValues'); + datalist .append('option') .attr('value', currYear) - .attr('label', currYear) + .attr('label', currYear); li .merge(liEnter) From 86023c0beab0d1c876e3f1bfb12c5f1886ed5dec Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Sat, 6 Jul 2024 13:48:36 +0200 Subject: [PATCH 27/44] fixed npm packages --- package-lock.json | 11 ----------- package.json | 1 - 2 files changed, 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index f61e49092..1ac917a92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,6 @@ "abortcontroller-polyfill": "^1.7.5", "aes-js": "^3.1.2", "alif-toolkit": "^1.2.9", - "all": "^0.0.0", "core-js-bundle": "^3.37.0", "diacritics": "1.3.0", "exifr": "^7.1.3", @@ -1903,11 +1902,6 @@ "version": "1.2.9", "license": "MIT" }, - "node_modules/all": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/all/-/all-0.0.0.tgz", - "integrity": "sha512-0oKlfNVv2d+d7c1gwjGspzgbwot47PGQ4b3v1ccx4mR8l9P/Y6E6Dr/yE8lNT63EcAKEbHo6UG3odDpC/NQcKw==" - }, "node_modules/amdefine": { "version": "1.0.1", "dev": true, @@ -10197,11 +10191,6 @@ "alif-toolkit": { "version": "1.2.9" }, - "all": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/all/-/all-0.0.0.tgz", - "integrity": "sha512-0oKlfNVv2d+d7c1gwjGspzgbwot47PGQ4b3v1ccx4mR8l9P/Y6E6Dr/yE8lNT63EcAKEbHo6UG3odDpC/NQcKw==" - }, "amdefine": { "version": "1.0.1", "dev": true diff --git a/package.json b/package.json index a2a5da8fb..1546a0f83 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "abortcontroller-polyfill": "^1.7.5", "aes-js": "^3.1.2", "alif-toolkit": "^1.2.9", - "all": "^0.0.0", "core-js-bundle": "^3.37.0", "diacritics": "1.3.0", "exifr": "^7.1.3", From fee3d4a3f7c8ef3c3d1416b4d0871d439ec20983 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Sat, 6 Jul 2024 14:47:05 +0200 Subject: [PATCH 28/44] trying to fix the date slider save thing arggg --- modules/renderer/photos.js | 18 +++++++++--------- modules/ui/sections/photo_overlays.js | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/renderer/photos.js b/modules/renderer/photos.js index 0fdae6a4b..97ed7225a 100644 --- a/modules/renderer/photos.js +++ b/modules/renderer/photos.js @@ -90,7 +90,7 @@ export function rendererPhotos(context) { } }; - photos.setFromDateFilter = function(date){ + photos.setFromDateFilter = function(date, updateUrl){ if (date !== -1){ var fromDate = new Date(); fromDate.setDate(fromDate.getDate() - date); @@ -99,21 +99,20 @@ export function rendererPhotos(context) { var yyyy = fromDate.getFullYear(); fromDate = mm + '/' + dd + '/' + yyyy; - photos.setDateFilter('fromDate', fromDate); + photos.setDateFilter('fromDate', fromDate, updateUrl); _maxPhotoDate = date; } else { - photos.setDateFilter('fromDate', null); + photos.setDateFilter('fromDate', null, updateUrl); } }; - photos.setFromYearFilter = function(year){ - if (year !== null){ - var fromDate = new Date(); - fromDate = '01/01/' + year; - photos.setDateFilter('fromDate', fromDate); + photos.setFromYearFilter = function(year, updateUrl){ + if (year){ + var fromDate = new Date(year, 0, 1); + photos.setDateFilter('fromDate', fromDate, updateUrl); _maxPhotoYear = year; } else { - photos.setDateFilter('fromDate', null); + photos.setDateFilter('fromDate', null, updateUrl); } }; @@ -221,6 +220,7 @@ export function rendererPhotos(context) { 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); + this.setFromYearFilter(parts && parts.length >= 2 && parts[1], false); } if (hash.photo_username) { this.setUsernameFilter(hash.photo_username, false); diff --git a/modules/ui/sections/photo_overlays.js b/modules/ui/sections/photo_overlays.js index ee892015b..6cc44bca2 100644 --- a/modules/ui/sections/photo_overlays.js +++ b/modules/ui/sections/photo_overlays.js @@ -402,7 +402,7 @@ export function uiSectionPhotoOverlays(context) { let value = parseInt(d3_select(this).property('value'), 10); let minYear = parseInt(d3_select(this).property('min'), 10); value = minYear + (currYear - value); - context.photos().setFromYearFilter(value); + context.photos().setFromYearFilter(value, true); output.html(value + ' - ' + currYear); }); From a6c667d9a916d659ecdb1208f92cd127e763ecfb Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Sun, 7 Jul 2024 15:19:13 +0200 Subject: [PATCH 29/44] still working on the slider --- modules/renderer/photos.js | 19 +++++----- modules/svg/panoramax_images.js | 34 +++++++++--------- modules/ui/sections/photo_overlays.js | 52 +++++++++++++-------------- 3 files changed, 53 insertions(+), 52 deletions(-) diff --git a/modules/renderer/photos.js b/modules/renderer/photos.js index 97ed7225a..a1a8a4e8f 100644 --- a/modules/renderer/photos.js +++ b/modules/renderer/photos.js @@ -14,7 +14,7 @@ export function rendererPhotos(context) { var _fromDate; var _toDate; var _maxPhotoDate; - var _maxPhotoYear; + var _yearSlider; var _usernames; function photos() {} @@ -52,8 +52,8 @@ export function rendererPhotos(context) { return _maxPhotoDate; }; - photos.maxPhotoYear = function() { - return _maxPhotoYear; + photos.yearSlider = function() { + return _yearSlider; }; photos.dateFilterValue = function(val) { @@ -106,14 +106,15 @@ export function rendererPhotos(context) { } }; - photos.setFromYearFilter = function(year, updateUrl){ - if (year){ - var fromDate = new Date(year, 0, 1); + photos.setFromYearFilter = function(currYear, updateUrl){ + if (currYear){ + var fromDate = new Date(currYear, 0, 1); photos.setDateFilter('fromDate', fromDate, updateUrl); - _maxPhotoYear = year; + _yearSlider = currYear; } else { photos.setDateFilter('fromDate', null, updateUrl); } + setUrlFilterValue('slider_date', currYear); }; @@ -220,7 +221,9 @@ export function rendererPhotos(context) { 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); - this.setFromYearFilter(parts && parts.length >= 2 && parts[1], false); + } + if (hash.slider_date){ + this.setFromYearFilter(hash.slider_date, false); } if (hash.photo_username) { this.setUsernameFilter(hash.photo_username, false); diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 7c311c007..0a191f3f4 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -187,7 +187,8 @@ export function svgPanoramaxImages(projection, context, dispatch) { } function updateYearSlider(oldestDate){ - let currYear = new Date(); + let maxYear = new Date(); + maxYear = maxYear.getFullYear(); let slider = d3_select('.list-option-date-slider'); let sliderWrap = slider.select(function() { return this.parentNode; }); @@ -198,27 +199,26 @@ export function svgPanoramaxImages(projection, context, dispatch) { .attr('id', 'dateValues') .attr('class', 'year-datalist'); - if (oldestDate > maxOldestYear){ - slider.attr('min', oldestDate); + if (oldestDate < maxOldestYear) oldestDate = maxOldestYear; - datalist - .append('option') - .attr('value', oldestDate) - .attr('label', oldestDate); - } else { - slider.attr('min', maxOldestYear); + if(slider.attr('value')){ + let currYear = parseInt(slider.attr('value'), 10); + currYear = oldestDate + (maxYear - currYear); + slider.attr('value', currYear) + } - datalist - .append('option') - .attr('value', maxOldestYear) - .attr('label', maxOldestYear); - }; + slider.attr('min', oldestDate); datalist .append('option') - .attr('value', currYear.getFullYear()) - .attr('label', currYear.getFullYear()); - }; + .attr('value', oldestDate) + .attr('label', oldestDate); + + datalist + .append('option') + .attr('value', maxYear) + .attr('label', maxYear); + } async function update() { const zoom = ~~context.map().zoom(); diff --git a/modules/ui/sections/photo_overlays.js b/modules/ui/sections/photo_overlays.js index 6cc44bca2..266116649 100644 --- a/modules/ui/sections/photo_overlays.js +++ b/modules/ui/sections/photo_overlays.js @@ -340,12 +340,18 @@ export function uiSectionPhotoOverlays(context) { function drawDateSlider(selection){ - function filterEnabled(d) { - return context.photos().maxPhotoYear(d); + function filterEnabled() { + return context.photos().yearSlider(); } - var currYear = new Date(); - currYear = parseInt(currYear.getFullYear(), 10); + var maxYear = new Date(); + maxYear = parseInt(maxYear.getFullYear(), 10); + + let yearSlider = context.photos().yearSlider(); + let currYear; + + if (yearSlider) currYear = yearSlider; + else currYear = maxYear; var ul = selection .selectAll('.layer-list-date-slider') @@ -369,15 +375,15 @@ export function uiSectionPhotoOverlays(context) { .append('li') .attr('class', 'list-item-date-slider'); - var labelEnter = liEnter - .append('label') - .each(function() { - d3_select(this) - .call(uiTooltip() - .title(() => t.append('photo_overlays.age_slider_filter.tooltip')) - .placement('top') - ); - }); + var labelEnter = liEnter + .append('label') + .each(function() { + d3_select(this) + .call(uiTooltip() + .title(() => t.append('photo_overlays.age_slider_filter.tooltip')) + .placement('top') + ); + }); labelEnter .append('span') @@ -389,33 +395,25 @@ export function uiSectionPhotoOverlays(context) { let output = sliderWrap .append('output') - .attr('class','year-selected'); + .attr('class','year-selected') + .html(currYear + ' - ' + maxYear); sliderWrap .append('input') .attr('type', 'range') - .attr('max', currYear) + .attr('max', maxYear) + .attr('value', currYear) .attr('list', 'dateValues') .attr('class', 'list-option-date-slider') .call(utilNoAuto) .on('change', function() { let value = parseInt(d3_select(this).property('value'), 10); let minYear = parseInt(d3_select(this).property('min'), 10); - value = minYear + (currYear - value); + value = minYear + (maxYear - value); context.photos().setFromYearFilter(value, true); - output.html(value + ' - ' + currYear); + output.html(value + ' - ' + maxYear); }); - let datalist = sliderWrap - .append('datalist') - .attr('class', 'year-datalist') - .attr('id', 'dateValues'); - - datalist - .append('option') - .attr('value', currYear) - .attr('label', currYear); - li .merge(liEnter) .classed('active', filterEnabled); From 91281df1df2e0f26d5c342cb4897a72eae5b3293 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Wed, 10 Jul 2024 10:35:32 +0200 Subject: [PATCH 30/44] the slider works now. --- modules/renderer/photos.js | 8 ++-- modules/services/panoramax.js | 4 +- modules/svg/panoramax_images.js | 64 ++++++++++++++++----------- modules/ui/sections/photo_overlays.js | 7 ++- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/modules/renderer/photos.js b/modules/renderer/photos.js index a1a8a4e8f..9a177afab 100644 --- a/modules/renderer/photos.js +++ b/modules/renderer/photos.js @@ -14,7 +14,7 @@ export function rendererPhotos(context) { var _fromDate; var _toDate; var _maxPhotoDate; - var _yearSlider; + var _yearSliderValue; var _usernames; function photos() {} @@ -52,8 +52,8 @@ export function rendererPhotos(context) { return _maxPhotoDate; }; - photos.yearSlider = function() { - return _yearSlider; + photos.yearSliderValue = function() { + return _yearSliderValue; }; photos.dateFilterValue = function(val) { @@ -110,7 +110,7 @@ export function rendererPhotos(context) { if (currYear){ var fromDate = new Date(currYear, 0, 1); photos.setDateFilter('fromDate', fromDate, updateUrl); - _yearSlider = currYear; + _yearSliderValue = currYear; } else { photos.setDateFilter('fromDate', null, updateUrl); } diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index ca99f8d2f..ecf925725 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -285,9 +285,7 @@ export default { }, getOldestDate: function(){ - if (_oldestDate){ - return _oldestDate.substr(0, 4); - } + return _oldestDate; }, // Get visible sequences diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 0a191f3f4..616ec75e1 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -186,38 +186,49 @@ export function svgPanoramaxImages(projection, context, dispatch) { if (service) service.setStyles(context, null); } - function updateYearSlider(oldestDate){ + function updateYearSlider(minYear){ let maxYear = new Date(); maxYear = maxYear.getFullYear(); let slider = d3_select('.list-option-date-slider'); - let sliderWrap = slider.select(function() { return this.parentNode; }); + if (slider && minYear){ + let sliderWrap = slider.select(function() { return this.parentNode; }); + let sliderLabel = sliderWrap.select('.year-selected'); - sliderWrap.selectAll('datalist').remove(); + sliderWrap.selectAll('datalist').remove(); - let datalist = sliderWrap.append('datalist') - .attr('id', 'dateValues') - .attr('class', 'year-datalist'); + let datalist = sliderWrap.append('datalist') + .attr('id', 'dateValues') + .attr('class', 'year-datalist'); - if (oldestDate < maxOldestYear) oldestDate = maxOldestYear; + minYear = parseInt(minYear, 10); - if(slider.attr('value')){ - let currYear = parseInt(slider.attr('value'), 10); - currYear = oldestDate + (maxYear - currYear); - slider.attr('value', currYear) + if (minYear < maxOldestYear) minYear = maxOldestYear; + + let currYear = sliderLabel.html(); + currYear = currYear.substring(0, 4); + currYear = parseInt(currYear, 10); + + let sliderValue = maxYear - (currYear - minYear); + + if (minYear > currYear){ + sliderValue = maxYear; + sliderLabel.html(minYear + ' - ' + maxYear); + } + + slider.attr('value', sliderValue); + slider.attr('min', minYear); + + datalist + .append('option') + .attr('value', minYear) + .attr('label', minYear); + + datalist + .append('option') + .attr('value', maxYear) + .attr('label', maxYear); } - - slider.attr('min', oldestDate); - - datalist - .append('option') - .attr('value', oldestDate) - .attr('label', oldestDate); - - datalist - .append('option') - .attr('value', maxYear) - .attr('label', maxYear); } async function update() { @@ -227,13 +238,12 @@ export function svgPanoramaxImages(projection, context, dispatch) { const service = getService(); let sequences = (service ? service.sequences(projection, zoom) : []); let images = (service ? service.images(projection) : []); - let oldestDate = service.getOldestDate(); - - updateYearSlider(oldestDate); images = await filterImages(images); sequences = await filterSequences(sequences, service); + let oldestDate = (service ? service.getOldestDate() : null); + let traces = layer.selectAll('.sequences').selectAll('.sequence') .data(sequences, function(d) { return d.id; }); @@ -297,6 +307,8 @@ export function svgPanoramaxImages(projection, context, dispatch) { .attr('transform', 'scale(1.5,1.5),translate(-8, -13)') .attr('d', viewfieldPath); + if (oldestDate) updateYearSlider(oldestDate.substring(0, 4)); + function viewfieldPath() { if (this.parentNode.__data__.isPano) { return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; diff --git a/modules/ui/sections/photo_overlays.js b/modules/ui/sections/photo_overlays.js index 266116649..a572b44eb 100644 --- a/modules/ui/sections/photo_overlays.js +++ b/modules/ui/sections/photo_overlays.js @@ -341,16 +341,16 @@ export function uiSectionPhotoOverlays(context) { function drawDateSlider(selection){ function filterEnabled() { - return context.photos().yearSlider(); + return context.photos().yearSliderValue(); } var maxYear = new Date(); maxYear = parseInt(maxYear.getFullYear(), 10); - let yearSlider = context.photos().yearSlider(); + let yearSliderValue = context.photos().yearSliderValue(); let currYear; - if (yearSlider) currYear = yearSlider; + if (yearSliderValue) currYear = yearSliderValue; else currYear = maxYear; var ul = selection @@ -402,7 +402,6 @@ export function uiSectionPhotoOverlays(context) { .append('input') .attr('type', 'range') .attr('max', maxYear) - .attr('value', currYear) .attr('list', 'dateValues') .attr('class', 'list-option-date-slider') .call(utilNoAuto) From ace965c2bb2a017f660f0a4d5706ecfa5514d0c0 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Wed, 10 Jul 2024 10:55:38 +0200 Subject: [PATCH 31/44] new endpoint! --- modules/services/panoramax.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index ecf925725..6f7e22ebc 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -10,7 +10,7 @@ import { t, localizer } from '../core/localizer'; import pannellumPhotoFrame from './pannellum_photo'; import planePhotoFrame from './plane_photo'; -const apiUrl = 'https://panoramax.openstreetmap.fr/'; +const apiUrl = 'https://api.panoramax.xyz/'; const tileUrl = apiUrl + 'api/map/{z}/{x}/{y}.mvt'; const imageBlobUrl = apiUrl + 'api/pictures/{pictureID}/{definition}.jpg'; const imageDataUrl = apiUrl + 'api/collections/{collectionId}/items/{itemId}'; From a9d8da10d13d66756be11163e9f0fd6309fcecc3 Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Fri, 12 Jul 2024 00:01:05 +0200 Subject: [PATCH 32/44] prepared for pr --- modules/renderer/photos.js | 10 +- modules/services/panoramax.js | 4 + modules/svg/layers.js | 2 +- modules/svg/panoramax_images.js | 52 +-------- modules/ui/sections/photo_overlays.js | 161 -------------------------- test/spec/services/panoramax.js | 115 ++++++++++++++++++ test/spec/svg/layers.js | 11 +- 7 files changed, 128 insertions(+), 227 deletions(-) create mode 100644 test/spec/services/panoramax.js diff --git a/modules/renderer/photos.js b/modules/renderer/photos.js index 9a177afab..ff10aa22a 100644 --- a/modules/renderer/photos.js +++ b/modules/renderer/photos.js @@ -157,15 +157,7 @@ export function rendererPhotos(context) { } photos.shouldFilterByDate = function() { - return showsLayer('mapillary') || showsLayer('kartaview') || showsLayer('streetside') || showsLayer('vegbilder'); - }; - - photos.shouldFilterByMaxAge = function(){ - return false; - }; - - photos.shouldFilterDateBySlider = function(){ - return showsLayer('panoramax'); + return showsLayer('mapillary') || showsLayer('kartaview') || showsLayer('streetside') || showsLayer('vegbilder') || showsLayer('panoramax'); }; photos.shouldFilterByPhotoType = function() { diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 6f7e22ebc..a08ac1db0 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -330,6 +330,10 @@ export default { } }, + getActiveImage: function(){ + return _activeImage; + }, + // Update the currently highlighted sequence and selected bubble. setStyles: function(context, hovered) { const hoveredImageId = hovered && hovered.id; diff --git a/modules/svg/layers.js b/modules/svg/layers.js index 4db0de629..59a014cc1 100644 --- a/modules/svg/layers.js +++ b/modules/svg/layers.js @@ -41,8 +41,8 @@ export function svgLayers(projection, context) { { id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) }, { id: 'kartaview', layer: svgKartaviewImages(projection, context, dispatch) }, { id: 'mapilio', layer: svgMapilioImages(projection, context, dispatch) }, - { id: 'panoramax', layer: svgPanoramaxImages(projection, context, dispatch) }, { id: 'vegbilder', layer: svgVegbilder(projection, context, dispatch) }, + { id: 'panoramax', layer: svgPanoramaxImages(projection, context, dispatch) }, { id: 'local-photos', layer: svgLocalPhotos(projection, context, dispatch) }, { id: 'debug', layer: svgDebug(projection, context, dispatch) }, { id: 'geolocate', layer: svgGeolocate(projection, context, dispatch) }, diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 616ec75e1..c536ea2ac 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -10,7 +10,6 @@ export function svgPanoramaxImages(projection, context, dispatch) { const imageMinZoom = 15; const lineMinZoom = 10; const viewFieldZoomLevel = 18; - const maxOldestYear = 2010; let layer = d3_select(null); let _panoramax; let _viewerYaw = 0; @@ -137,7 +136,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { function transform(d) { let t = svgPointTransform(projection)(d); var rot = d.heading + _viewerYaw; - if (rot) { + if (rot && !isNaN(rot)) { t += ' rotate(' + Math.floor(rot) + ',0,0)'; } return t; @@ -186,51 +185,6 @@ export function svgPanoramaxImages(projection, context, dispatch) { if (service) service.setStyles(context, null); } - function updateYearSlider(minYear){ - let maxYear = new Date(); - maxYear = maxYear.getFullYear(); - let slider = d3_select('.list-option-date-slider'); - - if (slider && minYear){ - let sliderWrap = slider.select(function() { return this.parentNode; }); - let sliderLabel = sliderWrap.select('.year-selected'); - - sliderWrap.selectAll('datalist').remove(); - - let datalist = sliderWrap.append('datalist') - .attr('id', 'dateValues') - .attr('class', 'year-datalist'); - - minYear = parseInt(minYear, 10); - - if (minYear < maxOldestYear) minYear = maxOldestYear; - - let currYear = sliderLabel.html(); - currYear = currYear.substring(0, 4); - currYear = parseInt(currYear, 10); - - let sliderValue = maxYear - (currYear - minYear); - - if (minYear > currYear){ - sliderValue = maxYear; - sliderLabel.html(minYear + ' - ' + maxYear); - } - - slider.attr('value', sliderValue); - slider.attr('min', minYear); - - datalist - .append('option') - .attr('value', minYear) - .attr('label', minYear); - - datalist - .append('option') - .attr('value', maxYear) - .attr('label', maxYear); - } - } - async function update() { const zoom = ~~context.map().zoom(); const showViewfields = (zoom >= viewFieldZoomLevel); @@ -242,8 +196,6 @@ export function svgPanoramaxImages(projection, context, dispatch) { images = await filterImages(images); sequences = await filterSequences(sequences, service); - let oldestDate = (service ? service.getOldestDate() : null); - let traces = layer.selectAll('.sequences').selectAll('.sequence') .data(sequences, function(d) { return d.id; }); @@ -307,8 +259,6 @@ export function svgPanoramaxImages(projection, context, dispatch) { .attr('transform', 'scale(1.5,1.5),translate(-8, -13)') .attr('d', viewfieldPath); - if (oldestDate) updateYearSlider(oldestDate.substring(0, 4)); - function viewfieldPath() { if (this.parentNode.__data__.isPano) { return 'M 8,13 m -10,0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0'; diff --git a/modules/ui/sections/photo_overlays.js b/modules/ui/sections/photo_overlays.js index a572b44eb..7d5c35ea2 100644 --- a/modules/ui/sections/photo_overlays.js +++ b/modules/ui/sections/photo_overlays.js @@ -30,7 +30,6 @@ export function uiSectionPhotoOverlays(context) { .merge(container) .call(drawPhotoItems) .call(drawPhotoTypeItems) - .call(drawDateSlider) .call(drawDateFilter) .call(drawUsernameFilter) .call(drawLocalPhotos); @@ -258,166 +257,6 @@ export function uiSectionPhotoOverlays(context) { .classed('active', filterEnabled); } - function drawMaxAgeFilter(selection){ - function filterEnabled(d) { - return context.photos().maxPhotoAge(d); - } - - var ul = selection - .selectAll('.layer-list-date-age') - .data([0]); - - ul.exit() - .remove(); - - ul = ul.enter() - .append('ul') - .attr('class', 'layer-list layer-list-date-age') - .merge(ul); - - var li = ul.selectAll('.list-item-date-age') - .data(context.photos().shouldFilterByMaxAge() ? ['max-age'] : []); - - li.exit() - .remove(); - - var liEnter = li.enter() - .append('li') - .attr('class', 'list-item-date-age'); - - var labelEnter = liEnter - .append('label') - .each(function() { - d3_select(this) - .call(uiTooltip() - .title(() => t.append('photo_overlays.max_age_filter.tooltip')) - .placement('top') - ); - }); - - labelEnter - .append('span') - .call(t.append('photo_overlays.max_age_filter.title')); - - labelEnter - .append('select') - .attr('type', 'text') - .attr('class', 'list-option') - .call(utilNoAuto); - - var select = labelEnter.selectAll('.list-option'); - - select - .append('option') - .attr('value', -1) - .call(t.append('photo_overlays.max_age_filter.all')); - - select - .append('option') - .attr('value', 7) - .call(t.append('photo_overlays.max_age_filter.week')); - - select - .append('option') - .attr('value', 31) - .call(t.append('photo_overlays.max_age_filter.month')); - - select - .append('option') - .attr('value', 365) - .call(t.append('photo_overlays.max_age_filter.year')); - - select - .on('change', function() { - var value = d3_select(this).property('value'); - context.photos().setMaxPhotoAge(parseInt(value, 10)); - }); - - li - .merge(liEnter) - .classed('active', filterEnabled); - } - - function drawDateSlider(selection){ - - function filterEnabled() { - return context.photos().yearSliderValue(); - } - - var maxYear = new Date(); - maxYear = parseInt(maxYear.getFullYear(), 10); - - let yearSliderValue = context.photos().yearSliderValue(); - let currYear; - - if (yearSliderValue) currYear = yearSliderValue; - else currYear = maxYear; - - var ul = selection - .selectAll('.layer-list-date-slider') - .data([0]); - - ul.exit() - .remove(); - - ul = ul.enter() - .append('ul') - .attr('class', 'layer-list layer-list-date-slider') - .merge(ul); - - var li = ul.selectAll('.list-item-date-slider') - .data(context.photos().shouldFilterDateBySlider() ? ['date-slider'] : []); - - li.exit() - .remove(); - - var liEnter = li.enter() - .append('li') - .attr('class', 'list-item-date-slider'); - - var labelEnter = liEnter - .append('label') - .each(function() { - d3_select(this) - .call(uiTooltip() - .title(() => t.append('photo_overlays.age_slider_filter.tooltip')) - .placement('top') - ); - }); - - labelEnter - .append('span') - .call(t.append('photo_overlays.age_slider_filter.title')); - - let sliderWrap = labelEnter - .append('div') - .attr('class','slider-wrap'); - - let output = sliderWrap - .append('output') - .attr('class','year-selected') - .html(currYear + ' - ' + maxYear); - - sliderWrap - .append('input') - .attr('type', 'range') - .attr('max', maxYear) - .attr('list', 'dateValues') - .attr('class', 'list-option-date-slider') - .call(utilNoAuto) - .on('change', function() { - let value = parseInt(d3_select(this).property('value'), 10); - let minYear = parseInt(d3_select(this).property('min'), 10); - value = minYear + (maxYear - value); - context.photos().setFromYearFilter(value, true); - output.html(value + ' - ' + maxYear); - }); - - li - .merge(liEnter) - .classed('active', filterEnabled); - } - function drawUsernameFilter(selection) { function filterEnabled() { return context.photos().usernames(); diff --git a/test/spec/services/panoramax.js b/test/spec/services/panoramax.js new file mode 100644 index 000000000..24c7176f2 --- /dev/null +++ b/test/spec/services/panoramax.js @@ -0,0 +1,115 @@ +describe('iD.servicePanoramax', function() { + var dimensions = [64, 64]; + var context, panoramax; + + before(function() { + iD.services.panoramax = iD.servicePanoramax; + fetchMock.reset(); + }); + + after(function() { + delete iD.services.panoramax; + }); + + beforeEach(function() { + context = iD.coreContext().assetPath('../dist/').init(); + context.projection + .scale(iD.geoZoomToScale(14)) + .translate([-116508, 0]) // 10,0 + .clipExtent([[0,0], dimensions]); + + panoramax = iD.services.panoramax; + panoramax.reset(); + fetchMock.reset(); + }); + + afterEach(function() { + fetchMock.reset(); + }); + + + describe('#init', function() { + it('Initializes cache one time', function() { + var cache = panoramax.cache(); + expect(cache).to.have.property('images'); + expect(cache).to.have.property('sequences'); + + panoramax.init(); + var cache2 = panoramax.cache(); + expect(cache).to.equal(cache2); + }); + }); + + describe('#reset', function() { + it('resets cache and image', function() { + panoramax.cache().foo = 'bar'; + panoramax.setActiveImage(context, {key: 'baz'}); + + panoramax.reset(); + expect(panoramax.cache()).to.not.have.property('foo'); + expect(panoramax.getActiveImage()).to.be.null; + }); + }); + + describe('#images', function() { + it('returns images in the visible map area', function() { + var features = [ + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { id: '0', loc: [10,0], heading: 90, sequence_id: '100', account_id: '0' } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { id: '1', loc: [10,0], heading: 90, sequence_id: '100', account_id: '1' } }, + { minX: 10, minY: 1, maxX: 10, maxY: 1, data: { id: '2', loc: [10,1], heading: 90, sequence_id: '100', account_id: '2' } } + ]; + + panoramax.cache().images.rtree.load(features); + var res = panoramax.images(context.projection); + + expect(res).to.deep.eql([ + { id: '0', loc: [10,0], heading: 90, sequence_id: '100', account_id: '0' }, + { id: '1', loc: [10,0], heading: 90, sequence_id: '100', account_id: '1' } + ]); + }); + + it('limits results no more than 5 stacked images in one spot', function() { + var features = [ + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { id: '0', loc: [10,0], heading: 90, sequence_id: '100', account_id: '0' } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { id: '1', loc: [10,0], heading: 90, sequence_id: '100', account_id: '1' } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { id: '2', loc: [10,0], heading: 90, sequence_id: '100', account_id: '2' } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { id: '3', loc: [10,0], heading: 90, sequence_id: '100', account_id: '3' } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { id: '4', loc: [10,0], heading: 90, sequence_id: '100', account_id: '4' } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { id: '5', loc: [10,0], heading: 90, sequence_id: '100', account_id: '5' } } + ]; + + panoramax.cache().images.rtree.load(features); + var res = panoramax.images(context.projection); + expect(res).to.have.length.of.at.most(5); + }); + }); + + + describe('#sequences', function() { + it('returns sequence linestrings in the visible map area', function() { + var features = [ + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { id: '0', loc: [10,0], heading: 90, sequence_id: '100', account_id: '0' } }, + { minX: 10, minY: 0, maxX: 10, maxY: 0, data: { id: '1', loc: [10,0], heading: 90, sequence_id: '100', account_id: '1' } }, + { minX: 10, minY: 1, maxX: 10, maxY: 1, data: { id: '2', loc: [10,1], heading: 90, sequence_id: '100', account_id: '2' } } + ]; + + panoramax.cache().images.rtree.load(features); + panoramax.cache().sequences.lineString['100'] = { rotation: 0, images: [ features[0].data, features[1].data, features[2].data ] }; + + var res = panoramax.sequences(context.projection, 14); + expect(res).to.deep.eql([{ + rotation: 0, images: [features[0].data, features[1].data, features[2].data] + }]); + }); + }); + + describe('#selectedImage', function() { + it('sets and gets selected image', function() { + var d = { id: 'foo', sequence_id: '100'}; + panoramax.cache().images = { forImageId: { foo: d }}; + panoramax.selectImage(context, 'foo'); + expect(panoramax.getActiveImage()).to.eql(d); + }); + }); + +}); diff --git a/test/spec/svg/layers.js b/test/spec/svg/layers.js index 6440d8b2b..4d600eb9c 100644 --- a/test/spec/svg/layers.js +++ b/test/spec/svg/layers.js @@ -26,7 +26,7 @@ describe('iD.svgLayers', function () { it('creates default data layers', function () { container.call(iD.svgLayers(projection, context)); var nodes = container.selectAll('svg .data-layer').nodes(); - expect(nodes.length).to.eql(18); + expect(nodes.length).to.eql(19); expect(d3.select(nodes[0]).classed('osm')).to.be.true; expect(d3.select(nodes[1]).classed('notes')).to.be.true; expect(d3.select(nodes[2]).classed('data')).to.be.true; @@ -41,10 +41,11 @@ describe('iD.svgLayers', function () { expect(d3.select(nodes[11]).classed('kartaview')).to.be.true; expect(d3.select(nodes[12]).classed('mapilio')).to.be.true; expect(d3.select(nodes[13]).classed('vegbilder')).to.be.true; - expect(d3.select(nodes[14]).classed('local-photos')).to.be.true; - expect(d3.select(nodes[15]).classed('debug')).to.be.true; - expect(d3.select(nodes[16]).classed('geolocate')).to.be.true; - expect(d3.select(nodes[17]).classed('touch')).to.be.true; + expect(d3.select(nodes[14]).classed('panoramax')).to.be.true; + expect(d3.select(nodes[15]).classed('local-photos')).to.be.true; + expect(d3.select(nodes[16]).classed('debug')).to.be.true; + expect(d3.select(nodes[17]).classed('geolocate')).to.be.true; + expect(d3.select(nodes[18]).classed('touch')).to.be.true; }); }); From 9a487728a08051cda8774d54f332fefad478dd2b Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Fri, 12 Jul 2024 00:07:46 +0200 Subject: [PATCH 33/44] added to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b56b93d5..3537fa51d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ _Breaking developer changes, which may affect downstream projects or sites that * Sort preset-specific optional fields before universal fields in "Add field" dropdown ([#10181], thanks [@zbycz]) #### :scissors: Operations #### :camera: Street-Level +* Added base functionalities for new Panoramax Street level ([#9941]) #### :white_check_mark: Validation #### :bug: Bugfixes * Fix bug which required a second button click when resolving/reopening of OSM notes ([#8994], thanks [@laigyu]) From ab69c4a3843e9c2b27f2fa0e46367f87abfda1bd Mon Sep 17 00:00:00 2001 From: Mattia Pezzotti <45800507+mattiapezzotti@users.noreply.github.com> Date: Sat, 13 Jul 2024 13:03:36 +0200 Subject: [PATCH 34/44] Update panoramax_images.js Fixing a bug with heading --- modules/svg/panoramax_images.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index c536ea2ac..cc106aa3e 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -136,7 +136,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { function transform(d) { let t = svgPointTransform(projection)(d); var rot = d.heading + _viewerYaw; - if (rot && !isNaN(rot)) { + if (rot) { t += ' rotate(' + Math.floor(rot) + ',0,0)'; } return t; From fe772db1ce137de5f683afa5614aeb3aef253945 Mon Sep 17 00:00:00 2001 From: Mattia Pezzotti <45800507+mattiapezzotti@users.noreply.github.com> Date: Sat, 13 Jul 2024 13:04:48 +0200 Subject: [PATCH 35/44] Update panoramax.js Fixing a bug with heading --- modules/services/panoramax.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index a08ac1db0..9113966c7 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -152,7 +152,7 @@ function loadTileDataToCache(data, tile) { id: feature.properties.id, account_id: feature.properties.account_id, sequence_id: feature.properties.sequences.split('\"')[1], - heading: feature.properties.heading, + heading: parseInt(feature.properties.heading, 10), image_path: '', resolution: feature.properties.resolution, isPano: feature.properties.type === 'equirectangular', From b0d9a2c7ade2b582ded138218560a85ed15d037f Mon Sep 17 00:00:00 2001 From: mattiapezzotti Date: Sun, 14 Jul 2024 10:13:16 +0200 Subject: [PATCH 36/44] fixing the mess --- modules/svg/panoramax_images.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index cc106aa3e..f5edef387 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -14,6 +14,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { let _panoramax; let _viewerYaw = 0; let _selectedSequence; + let _activeId; function init() { if (svgPanoramaxImages.initialized) return; @@ -62,7 +63,15 @@ export function svgPanoramaxImages(projection, context, dispatch) { }); } if (username && service) { - let id = await service.getUserId(username); + if (!_activeId) { + const tempId = await service.getUserId(username); + + if (!_activeId) { + _activeId = tempId; + } + } + + let id = _activeId; images = images.filter(function(image) { return id === image.account_id; @@ -98,7 +107,15 @@ export function svgPanoramaxImages(projection, context, dispatch) { }); } if (username && service) { - let id = await service.getUserId(username); + if (!_activeId) { + const tempId = await service.getUserId(username); + + if (!_activeId) { + _activeId = tempId; + } + } + + let id = _activeId; sequences = sequences.filter(function(sequence) { return id === sequence.properties.account_id; From b49262768949bc905f53a4e61446308aa983f964 Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 29 Jul 2024 15:36:26 +0200 Subject: [PATCH 37/44] switch link to panoramax.xyz viewer --- modules/services/panoramax.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 9113966c7..2520d71e3 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -16,6 +16,7 @@ const imageBlobUrl = apiUrl + 'api/pictures/{pictureID}/{definition}.jpg'; const imageDataUrl = apiUrl + 'api/collections/{collectionId}/items/{itemId}'; const userIdUrl = apiUrl + 'api/users/search?q={username}'; const usernameURL = apiUrl + 'api/users/{userId}'; +const viewerUrl = apiUrl; const highDefinition = 'hd'; const standardDefinition = 'sd'; @@ -388,7 +389,7 @@ export default { that.setActiveImage(d); that.updateUrlImage(d.id); - let imageUrl = getImageURL(d.id, highDefinition); + const viewerLink = `${viewerUrl}#pic=${d.id}&focus=pic`; let viewer = context.container() .select('.photoviewer'); @@ -457,8 +458,8 @@ export default { .append('a') .attr('class', 'image-link') .attr('target', '_blank') - .attr('href', imageUrl) - .text('panoramax.fr'); + .attr('href', viewerLink) + .text('panoramax.xyz'); getImageData(d.sequence_id, d.id).then(function(data){ _currentScene = { From df3b5a444069c8795e4d7561bc902a66737e6b0f Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 29 Jul 2024 15:36:40 +0200 Subject: [PATCH 38/44] =?UTF-8?q?make=20"captured=20by=20=E2=80=A6"=20tran?= =?UTF-8?q?slatable;=20single=20line=20if=20possible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- css/60_photos.css | 1 + data/core.yaml | 1 + modules/services/panoramax.js | 8 ++++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/css/60_photos.css b/css/60_photos.css index 516784f2f..abcbef7be 100644 --- a/css/60_photos.css +++ b/css/60_photos.css @@ -104,6 +104,7 @@ .photo-attribution span { padding: 4px 2px; color: #fff; + text-wrap: nowrap; } /* markers and sequences */ diff --git a/data/core.yaml b/data/core.yaml index 46f4e4968..f59331cdb 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -1438,6 +1438,7 @@ en: title: Panoramax tooltip: "Street-level photos from Panoramax" report: "Report" + captured_by: "Captured by {username}" hd: "High resolution" street_side: minzoom_tooltip: "Zoom in to see street-side photos" diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 2520d71e3..c200bf357 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -504,15 +504,19 @@ export default { } if (d.account_id) { + attribution + .append('span') + .text('|'); + let line2 = attribution - .append('div') + .append('span') .attr('class', 'attribution-row'); getUsername(d.account_id).then(function(username){ line2 .append('span') .attr('class', 'captured_by') - .text('Captured by: ' + username); + .text(t('panoramax.captured_by', {username})); }); } From 608251a6abae974d43a9f20828f1dce01fc3efdd Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 29 Jul 2024 15:47:24 +0200 Subject: [PATCH 39/44] update photos immediately when filters change --- modules/svg/mapilio_images.js | 2 +- modules/svg/panoramax_images.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/svg/mapilio_images.js b/modules/svg/mapilio_images.js index ac62b52ef..70c56775a 100644 --- a/modules/svg/mapilio_images.js +++ b/modules/svg/mapilio_images.js @@ -229,7 +229,7 @@ export function svgMapilioImages(projection, context, dispatch) { svgMapilioImages.enabled = _; if (svgMapilioImages.enabled) { showLayer(); - context.photos().on('change.mapilio_images', null); + context.photos().on('change.mapilio_images', update); } else { hideLayer(); context.photos().on('change.mapilio_images', null); diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index f5edef387..b74c42a74 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -355,7 +355,7 @@ export function svgPanoramaxImages(projection, context, dispatch) { svgPanoramaxImages.enabled = _; if (svgPanoramaxImages.enabled) { showLayer(); - context.photos().on('change.panoramax_images', null); + context.photos().on('change.panoramax_images', update); } else { hideLayer(); context.photos().on('change.panoramax_images', null); From f25e94c71be433aa351f58c76013ad28aa01a40c Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 29 Jul 2024 16:15:34 +0200 Subject: [PATCH 40/44] update username filter on input change; fix multiple names/ids in panoramax a username can resolve to multiple ids (when the same name is used on multiple servers). further, the username filter input field can contain more than one username --- modules/services/panoramax.js | 16 +++++++++------ modules/svg/panoramax_images.js | 35 +++++++++++++++++---------------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index c200bf357..521dcf817 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -274,15 +274,19 @@ export default { loadTiles('line', tileUrl, lineMinZoom, projection); }, - getUserId: async function(username){ - const requestUrl = userIdUrl.replace('{username}', username); + getUserIds: async function(usernames) { + const requestUrls = usernames.map(username => + userIdUrl.replace('{username}', username)); - const response = await fetch(requestUrl, { method: 'GET' }); - if (!response.ok) { + const responses = await Promise.all(requestUrls.map(requestUrl => + fetch(requestUrl, { method: 'GET' }))); + if (responses.some(response => !response.ok)) { throw new Error(response.status + ' ' + response.statusText); } - const data = await response.json(); - return data.features[0].id; + const data = await Promise.all(responses.map(response => response.json())); + // in panoramax, a username can have multiple ids, when the same name is + // used on different servers + return data.flatMap((d, i) => d.features.filter(f => f.name === usernames[i]).map(f => f.id)); }, getOldestDate: function(){ diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index b74c42a74..3e15f2898 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -14,7 +14,8 @@ export function svgPanoramaxImages(projection, context, dispatch) { let _panoramax; let _viewerYaw = 0; let _selectedSequence; - let _activeId; + let _activeUsernameFilter; + let _activeIds; function init() { if (svgPanoramaxImages.initialized) return; @@ -63,18 +64,18 @@ export function svgPanoramaxImages(projection, context, dispatch) { }); } if (username && service) { - if (!_activeId) { - const tempId = await service.getUserId(username); + if (_activeUsernameFilter !== username) { + const tempIds = await service.getUserIds(username); - if (!_activeId) { - _activeId = tempId; - } + _activeUsernameFilter = username; + _activeIds = {}; + tempIds.forEach(id => { + _activeIds[id] = true; + }) } - let id = _activeId; - images = images.filter(function(image) { - return id === image.account_id; + return _activeIds[image.account_id]; }); } @@ -107,18 +108,18 @@ export function svgPanoramaxImages(projection, context, dispatch) { }); } if (username && service) { - if (!_activeId) { - const tempId = await service.getUserId(username); + if (_activeUsernameFilter !== username) { + const tempIds = await service.getUserIds(username); - if (!_activeId) { - _activeId = tempId; - } + _activeUsernameFilter = username; + _activeIds = {}; + tempIds.forEach(id => { + _activeIds[id] = true; + }) } - let id = _activeId; - sequences = sequences.filter(function(sequence) { - return id === sequence.properties.account_id; + return _activeIds[sequence.properties.account_id]; }); } From bc103feaca8c97a3f77fece9e409788443365fa4 Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 29 Jul 2024 16:18:37 +0200 Subject: [PATCH 41/44] minor tweak of changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f65b85f2..2b51f0105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,7 +45,7 @@ _Breaking developer changes, which may affect downstream projects or sites that * Sort preset-specific optional fields before universal fields in "Add field" dropdown ([#10181], thanks [@zbycz]) #### :scissors: Operations #### :camera: Street-Level -* Added base functionalities for new Panoramax Street level ([#9941]) +* Add Panoramax as new street level imagery provider ([#9941], thanks [@mattiapezzotti]) #### :white_check_mark: Validation * Drop deprecated validation service _ImproveOSM_ ([#10302], thanks [@arch0345]) #### :bug: Bugfixes From cd0cd7b6d65d7cd7d52085c582d008cb98a63598 Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 29 Jul 2024 16:19:54 +0200 Subject: [PATCH 42/44] drop unused strings --- data/core.yaml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index f59331cdb..5cdb0658e 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -888,16 +888,6 @@ en: username_filter: title: "Username" tooltip: "Show only photos by this user" - max_age_filter: - year: "Last year" - month: "Last month" - week: "Last week" - all: "Show all" - title: "Photo age" - tooltip: "Select maximum photo age" - age_slider_filter: - title: "Year Slider" - tooltip: "Select oldest photo year" feature: points: description: Points From 4646b67bacfee0a8d6e39ac0cbde98dd78bcdb11 Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 29 Jul 2024 16:22:04 +0200 Subject: [PATCH 43/44] drop (for now) unused ui code --- modules/renderer/photos.js | 41 -------------------------------------- 1 file changed, 41 deletions(-) diff --git a/modules/renderer/photos.js b/modules/renderer/photos.js index ff10aa22a..b28fd9b2f 100644 --- a/modules/renderer/photos.js +++ b/modules/renderer/photos.js @@ -13,8 +13,6 @@ export function rendererPhotos(context) { var _dateFilters = ['fromDate', 'toDate']; var _fromDate; var _toDate; - var _maxPhotoDate; - var _yearSliderValue; var _usernames; function photos() {} @@ -48,14 +46,6 @@ export function rendererPhotos(context) { return _dateFilters; }; - photos.maxPhotoAge = function() { - return _maxPhotoDate; - }; - - photos.yearSliderValue = function() { - return _yearSliderValue; - }; - photos.dateFilterValue = function(val) { return val === _dateFilters[0] ? _fromDate : _toDate; }; @@ -90,34 +80,6 @@ export function rendererPhotos(context) { } }; - photos.setFromDateFilter = function(date, updateUrl){ - if (date !== -1){ - var fromDate = new Date(); - fromDate.setDate(fromDate.getDate() - date); - var dd = String(fromDate.getDate()).padStart(2, '0'); - var mm = String(fromDate.getMonth() + 1).padStart(2, '0'); - var yyyy = fromDate.getFullYear(); - - fromDate = mm + '/' + dd + '/' + yyyy; - photos.setDateFilter('fromDate', fromDate, updateUrl); - _maxPhotoDate = date; - } else { - photos.setDateFilter('fromDate', null, updateUrl); - } - }; - - photos.setFromYearFilter = function(currYear, updateUrl){ - if (currYear){ - var fromDate = new Date(currYear, 0, 1); - photos.setDateFilter('fromDate', fromDate, updateUrl); - _yearSliderValue = currYear; - } else { - photos.setDateFilter('fromDate', null, updateUrl); - } - setUrlFilterValue('slider_date', currYear); - }; - - photos.setUsernameFilter = function(val, updateUrl) { if (val && typeof val === 'string') val = val.replace(/;/g, ',').split(','); if (val) { @@ -214,9 +176,6 @@ export function rendererPhotos(context) { this.setDateFilter('fromDate', parts && parts.length >= 2 && parts[1], false); this.setDateFilter('toDate', parts && parts.length >= 3 && parts[2], false); } - if (hash.slider_date){ - this.setFromYearFilter(hash.slider_date, false); - } if (hash.photo_username) { this.setUsernameFilter(hash.photo_username, false); } From b4eaeb3088ac324ea0bb029c0e3a3ebd6bec0a0b Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 29 Jul 2024 16:29:39 +0200 Subject: [PATCH 44/44] lint: remove unused code, code style and early update cached username --- modules/services/panoramax.js | 10 +--------- modules/svg/panoramax_images.js | 10 ++++++---- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/modules/services/panoramax.js b/modules/services/panoramax.js index 521dcf817..20240551e 100644 --- a/modules/services/panoramax.js +++ b/modules/services/panoramax.js @@ -12,7 +12,6 @@ import planePhotoFrame from './plane_photo'; const apiUrl = 'https://api.panoramax.xyz/'; const tileUrl = apiUrl + 'api/map/{z}/{x}/{y}.mvt'; -const imageBlobUrl = apiUrl + 'api/pictures/{pictureID}/{definition}.jpg'; const imageDataUrl = apiUrl + 'api/collections/{collectionId}/items/{itemId}'; const userIdUrl = apiUrl + 'api/users/search?q={username}'; const usernameURL = apiUrl + 'api/users/{userId}'; @@ -199,14 +198,6 @@ function loadTileDataToCache(data, tile) { } } -// Quick access to image -function getImageURL(image_id, definition){ - const requestUrl = imageBlobUrl.replace('{pictureID}', image_id) - .replace('{definition}', definition); - - return requestUrl; -} - async function getImageData(collection_id, image_id){ const requestUrl = imageDataUrl.replace('{collectionId}', collection_id) .replace('{itemId}', image_id); @@ -281,6 +272,7 @@ export default { const responses = await Promise.all(requestUrls.map(requestUrl => fetch(requestUrl, { method: 'GET' }))); if (responses.some(response => !response.ok)) { + const response = responses.find(response => !response.ok); throw new Error(response.status + ' ' + response.statusText); } const data = await Promise.all(responses.map(response => response.json())); diff --git a/modules/svg/panoramax_images.js b/modules/svg/panoramax_images.js index 3e15f2898..20f0df37d 100644 --- a/modules/svg/panoramax_images.js +++ b/modules/svg/panoramax_images.js @@ -65,13 +65,14 @@ export function svgPanoramaxImages(projection, context, dispatch) { } if (username && service) { if (_activeUsernameFilter !== username) { + _activeUsernameFilter = username; + const tempIds = await service.getUserIds(username); - _activeUsernameFilter = username; _activeIds = {}; tempIds.forEach(id => { _activeIds[id] = true; - }) + }); } images = images.filter(function(image) { @@ -109,13 +110,14 @@ export function svgPanoramaxImages(projection, context, dispatch) { } if (username && service) { if (_activeUsernameFilter !== username) { + _activeUsernameFilter = username; + const tempIds = await service.getUserIds(username); - _activeUsernameFilter = username; _activeIds = {}; tempIds.forEach(id => { _activeIds[id] = true; - }) + }); } sequences = sequences.filter(function(sequence) {