Skip to content

Commit 1e7bb3f

Browse files
committed
Add start and end date filters
1 parent cb97bad commit 1e7bb3f

3 files changed

Lines changed: 113 additions & 11 deletions

File tree

assets/src/components/Panoramax.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ export default class Panoramax extends HTMLElement {
6868
this._psv.goToPosition(e.lat, e.lon);
6969
};
7070
mainEventDispatcher.addListener(this._onPositionSelected, 'panoramax.position.selected');
71+
72+
// Date filter: read both inputs and push the new range to the module.
73+
this._onDateChange = () => {
74+
const start = this.querySelector('input[data-filter="start"]')?.value || null;
75+
const end = this.querySelector('input[data-filter="end"]')?.value || null;
76+
mainLizmap.panoramax?.setDateFilter(start, end);
77+
};
7178
}
7279

7380
disconnectedCallback() {
@@ -103,10 +110,22 @@ export default class Panoramax extends HTMLElement {
103110
await import(/* webpackChunkName: 'panoramax-viewer' */ '@panoramax/web-viewer');
104111

105112
render(
106-
html`<pnx-photo-viewer
107-
endpoint="${endpoint}"
108-
style="display:block;width:100%;height:100%;min-height:400px;"
109-
></pnx-photo-viewer>`,
113+
html`
114+
<div class="d-flex flex-column h-100">
115+
<pnx-photo-viewer
116+
class="panoramax-viewer"
117+
endpoint="${endpoint}"
118+
></pnx-photo-viewer>
119+
<div class="panoramax-date-filter d-flex align-items-center gap-2 px-2 py-1 border-top flex-shrink-0">
120+
<input type="date" class="form-control form-control-sm" data-filter="start"
121+
aria-label="Start date" title="Start date"
122+
@change=${this._onDateChange}>
123+
<span class="text-muted"></span>
124+
<input type="date" class="form-control form-control-sm" data-filter="end"
125+
aria-label="End date" title="End date"
126+
@change=${this._onDateChange}>
127+
</div>
128+
</div>`,
110129
this
111130
);
112131
this._viewer = this.querySelector('pnx-photo-viewer');

assets/src/modules/Panoramax.js

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@ import Feature from 'ol/Feature.js';
1515
import Point from 'ol/geom/Point.js';
1616
import Style from 'ol/style/Style.js';
1717
import Icon from 'ol/style/Icon.js';
18+
import CircleStyle from 'ol/style/Circle.js';
19+
import Stroke from 'ol/style/Stroke.js';
20+
import Fill from 'ol/style/Fill.js';
1821

1922
/** Default Panoramax instance used when `panoramaxUrl` is not set in the config */
2023
const DEFAULT_PANORAMAX_URL = 'https://panoramax.openstreetmap.fr/api';
2124

25+
/** Panoramax brand color used for the coverage layer */
26+
const PNX_COLOR = '#e2007a';
27+
2228
/**
2329
* Arrow icon pointing to the North (heading 0). Rotated clockwise to match the
2430
* heading (in radians) reported by the Panoramax photo viewer.
2531
*/
2632
const ARROW_SVG = 'data:image/svg+xml,'
2733
+ "%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48' width='48' height='48'%3E"
28-
+ "%3Cpath d='M24 3 41 43 24 33 7 43Z' fill='%23e2007a' stroke='%23ffffff' stroke-width='2.5' stroke-linejoin='round'/%3E"
34+
+ "%3Cpath d='M24 3 41 43 24 33 7 43Z' fill='%231700e2' stroke='%23ffffff' stroke-width='2.5' stroke-linejoin='round'/%3E"
2935
+ '%3C/svg%3E';
3036

3137
/**
@@ -53,10 +59,31 @@ export default class Panoramax {
5359
this._lizmap3 = lizmap3;
5460
this._active = false;
5561

62+
// Date-range filter state (null = no filter).
63+
// - sequences layer uses the `date` field ("YYYY-MM-DD")
64+
// - pictures layer uses the `ts` field ("YYYY-MM-DDTHH:mm:ss")
65+
this._filterStart = null;
66+
this._filterEnd = null;
67+
// `ts` includes time, so the max comparison uses end+1 day to include
68+
// all pictures taken on the end day (mirrors Panoramax's own logic).
69+
this._filterEndPlusOne = null;
70+
5671
// STAC API base URL and the derived MVT tiles URL
5772
this._url = (options.panoramaxUrl || DEFAULT_PANORAMAX_URL).replace(/\/+$/, '');
5873
const tilesUrl = this._url + '/map/{z}/{x}/{y}.mvt';
5974

75+
// Cached styles for the coverage layer (created once, reused every frame).
76+
this._pointStyle = new Style({
77+
image: new CircleStyle({
78+
radius: 4,
79+
fill: new Fill({ color: PNX_COLOR }),
80+
stroke: new Stroke({ color: '#ffffff', width: 1 }),
81+
}),
82+
});
83+
this._lineStyle = new Style({
84+
stroke: new Stroke({ color: PNX_COLOR, width: 3 }),
85+
});
86+
6087
// Panoramax coverage layer (MVT). The tiles are served in EPSG:3857 and
6188
// reprojected by OpenLayers to the current map view projection if needed.
6289
this._olLayer = new VectorTileLayer({
@@ -65,7 +92,27 @@ export default class Panoramax {
6592
format: new MVT(),
6693
projection: 'EPSG:3857',
6794
url: tilesUrl,
68-
})
95+
}),
96+
style: (feature) => {
97+
const isPoint = feature.getType() === 'Point' || feature.getType() === 'MultiPoint';
98+
// Apply date filter when at least one bound is set.
99+
if (this._filterStart || this._filterEnd) {
100+
// sequences → `date` ("YYYY-MM-DD"), pictures → `ts` ("YYYY-MM-DDTHH:mm:ss")
101+
const dateStr = isPoint ? feature.get('ts') : feature.get('date');
102+
if (dateStr) {
103+
if (this._filterStart && dateStr < this._filterStart) {
104+
return null;
105+
}
106+
if (this._filterEnd) {
107+
const maxComp = isPoint ? this._filterEndPlusOne : this._filterEnd;
108+
if (dateStr > maxComp) {
109+
return null;
110+
}
111+
}
112+
}
113+
}
114+
return isPoint ? this._pointStyle : this._lineStyle;
115+
},
69116
});
70117

71118
// Add the layer to the layer tree through an external group. This must be
@@ -217,9 +264,14 @@ export default class Panoramax {
217264
picId = props.id ?? props.picture_id ?? props.pic_id ?? fid ?? null;
218265
seqId = props.sequence_id ?? props.seq_id ?? null;
219266
if (!seqId && props.sequences) {
220-
seqId = Array.isArray(props.sequences)
221-
? props.sequences[0]
222-
: String(props.sequences).split(',')[0];
267+
let sequences = props.sequences;
268+
// MVT RenderFeature serializes JS arrays as JSON strings (e.g. '["uuid1"]').
269+
if (typeof sequences === 'string' && sequences.trimStart().startsWith('[')) {
270+
try { sequences = JSON.parse(sequences); } catch { sequences = []; }
271+
}
272+
seqId = Array.isArray(sequences)
273+
? (sequences[0] ?? null)
274+
: (String(sequences) || null);
223275
}
224276
}
225277
// A sequence line (no precise picture id) falls through to the
@@ -242,4 +294,29 @@ export default class Panoramax {
242294
});
243295
}
244296
}
297+
298+
/**
299+
* Filter the coverage layer to only show pictures/sequences in a date range.
300+
* Either bound can be null to leave that side open.
301+
* @param {string|null} startDate - ISO date string "YYYY-MM-DD", or null
302+
* @param {string|null} endDate - ISO date string "YYYY-MM-DD", or null
303+
*/
304+
setDateFilter(startDate, endDate) {
305+
this._filterStart = startDate || null;
306+
this._filterEnd = endDate || null;
307+
308+
if (endDate) {
309+
// Advance by one day so that pictures with a timestamp on `endDate`
310+
// (e.g. "2024-03-15T14:30:00") are included in the result.
311+
const d = new Date(endDate);
312+
d.setDate(d.getDate() + 1);
313+
this._filterEndPlusOne = d.toISOString().split('T')[0];
314+
} else {
315+
this._filterEndPlusOne = null;
316+
}
317+
318+
// Mark the layer dirty so the style function is re-evaluated on the next
319+
// render frame. The tiles themselves remain cached (no network requests).
320+
this._olLayer.changed();
321+
}
245322
}

lizmap/www/assets/css/map.css

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,11 +833,17 @@ CSS Sprite positions calculated via http://instantsprite.com/ using 20px images
833833

834834
/* Panoramax */
835835
lizmap-panoramax {
836-
width: 400px;
837-
height: 350px;
836+
width: 500px;
837+
height: 450px;
838838
display: block;
839839
}
840840

841+
lizmap-panoramax .panoramax-viewer {
842+
display: block;
843+
flex: 1;
844+
min-height: 300px;
845+
}
846+
841847
/* Panoramax logo icon */
842848
#mapmenu .nav-list > li.panoramax .icon {
843849
background-image: none;

0 commit comments

Comments
 (0)