@panoramax/web-viewer 4.1.0-develop-616eda58 → 4.1.0-develop-c921ed43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panoramax/web-viewer",
3
- "version": "4.1.0-develop-616eda58",
3
+ "version": "4.1.0-develop-c921ed43",
4
4
  "description": "Panoramax web viewer for geolocated pictures",
5
5
  "main": "build/index.js",
6
6
  "author": "Panoramax team",
@@ -131,6 +131,7 @@ export default class Editor extends Basic {
131
131
  }
132
132
  super.connectedCallback();
133
133
 
134
+ this._moveChildToGrid();
134
135
  window.addEventListener("DOMContentLoaded", () => {
135
136
  this._moveChildToGrid();
136
137
  }, { once: true });
@@ -199,6 +199,7 @@ export default class PhotoViewer extends Basic {
199
199
 
200
200
  this.onceAPIReady().then(this._postAPIInit.bind(this));
201
201
 
202
+ this._moveChildToGrid();
202
203
  window.addEventListener("DOMContentLoaded", () => {
203
204
  this._moveChildToGrid();
204
205
  }, { once: true });
@@ -4,7 +4,7 @@ import "./Viewer.css";
4
4
  import { linkMapAndPhoto, saveMapParamsToLocalStorage, getMapParamsFromLocalStorage } from "../../utils/map";
5
5
  import PhotoViewer, {KEYBOARD_SKIP_FOCUS_WIDGETS} from "./PhotoViewer";
6
6
  import MapMore from "../ui/MapMore";
7
- import { initMapKeyboardHandler } from "../../utils/map";
7
+ import { initMapKeyboardHandler, mapFiltersFormValues } from "../../utils/map";
8
8
  import { isNullId, isInIframe } from "../../utils/utils";
9
9
  import { createWebComp } from "../../utils/widgets";
10
10
  import { fa } from "../../utils/widgets";
@@ -569,35 +569,8 @@ export default class Viewer extends PhotoViewer {
569
569
  */
570
570
  _onMapFiltersChange() {
571
571
  const mapFiltersMenu = querySelectorDeep("#pnx-map-filters-menu");
572
- const fMinDate = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-date-from");
573
- const fMaxDate = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-date-end");
574
- const fTypes = mapFiltersMenu?.shadowRoot.querySelectorAll("input[name='pnx-filter-type']");
575
572
  const fMapTheme = querySelectorDeep("#pnx-map-theme");
576
-
577
- let type = "";
578
- for(let fTypeId = 0 ; fTypeId < fTypes.length; fTypeId++) {
579
- const fType = fTypes[fTypeId];
580
- if(fType.checked) {
581
- type = fType.value;
582
- break;
583
- }
584
- }
585
-
586
- let qualityscore = [];
587
- if(this.map?._hasQualityScore()) {
588
- const fScore = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-qualityscore");
589
- qualityscore = (fScore?.grade || "").split(",").map(v => parseInt(v)).filter(v => !isNaN(v));
590
- if(qualityscore.length == 5) { qualityscore = []; }
591
- }
592
-
593
- const values = {
594
- minDate: fMinDate?.value,
595
- maxDate: fMaxDate?.value,
596
- pic_type: type,
597
- theme: fMapTheme?.value,
598
- qualityscore,
599
- };
600
-
573
+ const values = mapFiltersFormValues(mapFiltersMenu, fMapTheme, this.map?._hasQualityScore());
601
574
  this.map.setFilters(values);
602
575
  }
603
576
  }
@@ -284,6 +284,20 @@ export default class MapFilters extends LitElement {
284
284
 
285
285
  if(dateToField) { dateToField.value = ""; }
286
286
  }
287
+
288
+ /** @private */
289
+ _onMeUserSearch() {
290
+ const userAccount = getUserAccount();
291
+ if(!userAccount) { return; }
292
+
293
+ const userField = this.shadowRoot.getElementById("pnx-filter-search-user");
294
+ if(!this._parent?.map.getVisibleUsers().includes(userAccount.id)) {
295
+ userField?._onResultClick({title: userAccount.name, data: {id: userAccount.id }});
296
+ }
297
+ else {
298
+ userField?._onResultClick();
299
+ }
300
+ }
287
301
 
288
302
  /** @private */
289
303
  render() {
@@ -386,7 +400,9 @@ export default class MapFilters extends LitElement {
386
400
  <h4>${fa(faUser)} ${this._parent?._t.pnx.filter_user}</h4>
387
401
  ${userAccount ? html`
388
402
  <div class="pnx-input-shortcuts">
389
- <button>${this._parent?._t.pnx.filter_user_mypics}</button>
403
+ <button @click=${this._onMeUserSearch}>
404
+ ${this._parent?._t.pnx.filter_user_mypics}
405
+ </button>
390
406
  </div>
391
407
  ` : nothing}
392
408
  <pnx-search-bar
@@ -1,13 +1,6 @@
1
1
  import Map from "./Map";
2
2
  import { COLORS, QUALITYSCORE_VALUES } from "../../utils/utils";
3
- import { TILES_PICTURES_ZOOM, MAP_EXPR_QUALITYSCORE, switchCoefValue } from "../../utils/map";
4
-
5
- const MAP_THEMES = {
6
- DEFAULT: "default",
7
- AGE: "age",
8
- TYPE: "type",
9
- SCORE: "score",
10
- };
3
+ import { MAP_EXPR_QUALITYSCORE, switchCoefValue, MAP_THEMES, mapFiltersToLayersFilters } from "../../utils/map";
11
4
 
12
5
  export const MAP_FILTERS = [ "minDate", "maxDate", "pic_type", "camera", "theme", "qualityscore"];
13
6
 
@@ -198,80 +191,6 @@ export default class MapMore extends Map {
198
191
  return s;
199
192
  }
200
193
 
201
- /** @private */
202
- _mapFiltersToLayersFilters(filters) {
203
- let mapFilters = {};
204
- let mapSeqFilters = [];
205
- let mapPicFilters = [];
206
- let reloadMapStyle = false;
207
-
208
- if(filters.minDate && filters.minDate !== "") {
209
- mapFilters.minDate = filters.minDate;
210
- mapSeqFilters.push([">=", ["get", "date"], filters.minDate]);
211
- mapPicFilters.push([">=", ["get", "ts"], filters.minDate]);
212
- }
213
-
214
- if(filters.maxDate && filters.maxDate !== "") {
215
- mapFilters.maxDate = filters.maxDate;
216
- mapSeqFilters.push(["<=", ["get", "date"], filters.maxDate]);
217
-
218
- // Get tomorrow date for pictures filtering
219
- // (because ts is date+time, so comparing date only string would fail otherwise)
220
- let d = new Date(filters.maxDate);
221
- d.setDate(d.getDate() + 1);
222
- d = d.toISOString().split("T")[0];
223
- mapPicFilters.push(["<=", ["get", "ts"], d]);
224
- }
225
-
226
- if(filters.pic_type && filters.pic_type !== "") {
227
- mapFilters.pic_type = filters.pic_type === "flat" ? "flat" : "equirectangular";
228
- mapSeqFilters.push(["==", ["get", "type"], mapFilters.pic_type]);
229
- mapPicFilters.push(["==", ["get", "type"], mapFilters.pic_type]);
230
- }
231
- if(this._hasGridStats()) {
232
- reloadMapStyle = true;
233
- }
234
-
235
- if(filters.camera && filters.camera !== "") {
236
- mapFilters.camera = filters.camera;
237
- // low/high model hack : to enable fuzzy filtering of camera make and model
238
- const lowModel = filters.camera.toLowerCase().trim() + " ";
239
- const highModel = filters.camera.toLowerCase().trim() + "zzzzzzzzzzzzzzzzzzzz";
240
- const collator = ["collator", { "case-sensitive": false, "diacritic-sensitive": false } ];
241
- mapSeqFilters.push([">=", ["get", "model"], lowModel, collator]);
242
- mapSeqFilters.push(["<=", ["get", "model"], highModel, collator]);
243
- mapPicFilters.push([">=", ["get", "model"], lowModel, collator]);
244
- mapPicFilters.push(["<=", ["get", "model"], highModel, collator]);
245
- }
246
-
247
- if(filters.qualityscore && filters.qualityscore.length > 0) {
248
- mapFilters.qualityscore = filters.qualityscore;
249
- mapSeqFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", mapFilters.qualityscore]]);
250
- mapPicFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", mapFilters.qualityscore]]);
251
- }
252
-
253
- if(filters.theme && Object.values(MAP_THEMES).includes(filters.theme)) {
254
- mapFilters.theme = filters.theme;
255
- reloadMapStyle = true;
256
- }
257
-
258
- if(mapSeqFilters.length == 0) { mapSeqFilters = null; }
259
- else {
260
- mapSeqFilters.unshift("all");
261
- }
262
-
263
- if(mapPicFilters.length == 0) { mapPicFilters = null; }
264
- else {
265
- mapPicFilters.unshift("all");
266
- mapPicFilters = ["step", ["zoom"],
267
- true,
268
- TILES_PICTURES_ZOOM, mapPicFilters
269
- ];
270
- }
271
-
272
- return { mapFilters, mapSeqFilters, mapPicFilters, reloadMapStyle };
273
- }
274
-
275
194
  /**
276
195
  * Change the map filters
277
196
  * @param {object} filters Filtering values
@@ -285,7 +204,7 @@ export default class MapMore extends Map {
285
204
  * @memberof Panoramax.components.core.MapMore#
286
205
  */
287
206
  setFilters(filters, skipZoomIn = false) {
288
- let { mapFilters, mapSeqFilters, mapPicFilters, reloadMapStyle } = this._mapFiltersToLayersFilters(filters);
207
+ let { mapFilters, mapSeqFilters, mapPicFilters, reloadMapStyle } = mapFiltersToLayersFilters(filters, this._hasGridStats());
289
208
 
290
209
  this._mapFilters = mapFilters;
291
210
  if(reloadMapStyle) {
@@ -340,7 +259,7 @@ export default class MapMore extends Map {
340
259
  await super.setVisibleUsers(visibleIds);
341
260
 
342
261
  // Force reload of styles & filters
343
- let { mapSeqFilters, mapPicFilters, reloadMapStyle } = this._mapFiltersToLayersFilters(this._mapFilters);
262
+ let { mapSeqFilters, mapPicFilters, reloadMapStyle } = mapFiltersToLayersFilters(this._mapFilters, this._hasGridStats());
344
263
 
345
264
  if(reloadMapStyle) {
346
265
  this.reloadLayersStyles();
@@ -70,7 +70,7 @@ export default class MapFiltersButton extends LitElement {
70
70
  /** @private */
71
71
  render() {
72
72
  const isSmall = this._parent?.isWidthSmall();
73
- const resetIcon = fa(faXmark);
73
+ const resetIcon = fa(faXmark, {styles: {"pointer-events": "auto"}});
74
74
  resetIcon.addEventListener("click", e => {
75
75
  e.stopPropagation();
76
76
  this.shadowRoot.querySelector("pnx-map-filters-menu")._onReset();
@@ -46,7 +46,7 @@ export default class MapLayersButton extends LitElement {
46
46
  /** @private */
47
47
  render() {
48
48
  const isSmall = this._parent?.isWidthSmall();
49
- const resetIcon = fa(faXmark);
49
+ const resetIcon = fa(faXmark, {styles: {"pointer-events": "auto"}});
50
50
  resetIcon.addEventListener("click", e => {
51
51
  e.stopPropagation();
52
52
  const menu = this.shadowRoot.querySelector("pnx-map-layers-menu");
@@ -289,13 +289,34 @@ export function xyzParamToPSVPosition(str) {
289
289
  * @private
290
290
  */
291
291
  function xywhParamToPSVPosition(str, picmeta) {
292
- const loc = (str || "").split(",");
292
+ let loc = (str || "").split(",");
293
293
  if (loc.length === 4 && !loc.some(v => isNaN(v))) {
294
- const size = picmeta?.properties?.["pers:interior_orientation"]?.sensor_array_dimensions;
294
+ let [ locX, locY, locW, locH ] = loc.map(v => parseFloat(v));
295
+ let basePanoData = picmeta.panorama.basePanoData;
296
+ if(typeof basePanoData === "function") {
297
+ const size = picmeta?.properties?.["pers:interior_orientation"]?.sensor_array_dimensions;
298
+ if(size && size.length == 2) { basePanoData = basePanoData({width: size[0], height: size[1]}); }
299
+ else { basePanoData = null; }
300
+ }
301
+
302
+ if(!basePanoData?.fullWidth || !basePanoData?.fullHeight) { return null; }
303
+
304
+ const croppedX = basePanoData?.croppedX || 0;
305
+ const croppedY = basePanoData?.croppedY || 0;
306
+
307
+ // Compute offset from texture center
308
+ const xOffsetPx = croppedX + locX + locW / 2 - basePanoData.fullWidth / 2;
309
+ const yOffsetPx = -(croppedY + locY + locH / 2 - basePanoData.fullHeight / 2);
310
+ const xOffsetPct = xOffsetPx / basePanoData.fullWidth;
311
+ const yOffsetPct = yOffsetPx / basePanoData.fullHeight;
312
+ const xSizePct = locW / basePanoData.fullWidth;
313
+ const ySizePct = locH / basePanoData.fullHeight;
314
+ const sizePct = (xSizePct + ySizePct) / 2;
315
+
295
316
  const res = {
296
- textureX: +loc[0] + loc[2] / 2,
297
- textureY: +loc[1] + loc[3] / 2,
298
- z: size && size.length == 2 ? (1 - (((loc[2] / size[0]) + (loc[3] / size[1])) / 2)) * 75 : null,
317
+ yaw: xOffsetPct * 2 * Math.PI,
318
+ pitch: yOffsetPct * Math.PI,
319
+ z: (1 - sizePct) * 100
299
320
  };
300
321
 
301
322
  return res;
package/src/utils/map.js CHANGED
@@ -62,6 +62,13 @@ export const VECTOR_STYLES = {
62
62
  }
63
63
  };
64
64
 
65
+ export const MAP_THEMES = {
66
+ DEFAULT: "default",
67
+ AGE: "age",
68
+ TYPE: "type",
69
+ SCORE: "score",
70
+ };
71
+
65
72
 
66
73
  // See MapLibre docs for explanation of expressions magic: https://maplibre.org/maplibre-style-spec/expressions/
67
74
  const MAP_EXPR_QUALITYSCORE_RES_360 = ["case", ["has", "h_pixel_density"], ["step", ["get", "h_pixel_density"], ...QUALITYSCORE_RES_360_VALUES], 1];
@@ -362,6 +369,122 @@ export function getMissingLayerStyles(sources = {}, layers = []) {
362
369
  return newLayers;
363
370
  }
364
371
 
372
+ /**
373
+ * Get comprehensive values from map filters form
374
+ * @param {Element} mapFiltersMenu The MapFilters DOM Element
375
+ * @param {Element} mapThemeSelect The Map Theme select DOM Element
376
+ * @param {boolean} hasQualityScore Set to true if vector tiles embed quality score data
377
+ * @return {object} The map filters as JSON
378
+ */
379
+ export function mapFiltersFormValues(mapFiltersMenu, mapThemeSelect, hasQualityScore) {
380
+ const fMinDate = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-date-from");
381
+ const fMaxDate = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-date-end");
382
+ const fTypes = mapFiltersMenu?.shadowRoot.querySelectorAll("input[name='pnx-filter-type']");
383
+
384
+ let type = "";
385
+ for(let fTypeId = 0 ; fTypeId < fTypes.length; fTypeId++) {
386
+ const fType = fTypes[fTypeId];
387
+ if(fType.checked) {
388
+ type = fType.value;
389
+ break;
390
+ }
391
+ }
392
+
393
+ let qualityscore = [];
394
+ if(hasQualityScore) {
395
+ const fScore = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-qualityscore");
396
+ qualityscore = (fScore?.grade || "").split(",").map(v => parseInt(v)).filter(v => !isNaN(v));
397
+ if(qualityscore.length == 5) { qualityscore = []; }
398
+ }
399
+
400
+ return {
401
+ minDate: fMinDate?.value,
402
+ maxDate: fMaxDate?.value,
403
+ pic_type: type,
404
+ theme: mapThemeSelect?.value,
405
+ qualityscore,
406
+ };
407
+ }
408
+
409
+ /**
410
+ * Transforms map filters into MapLibre-like JSON filters
411
+ * @param {object} filters Filters returned by MapFilters widgets
412
+ * @param {boolean} [hasGridStats=false] Set to true if vector tiles offer grid statistics
413
+ * @returns {object} Set of variables { mapFilters, mapSeqFilters, mapPicFilters, reloadMapStyle }
414
+ */
415
+ export function mapFiltersToLayersFilters(filters, hasGridStats = false) {
416
+ let mapFilters = {};
417
+ let mapSeqFilters = [];
418
+ let mapPicFilters = [];
419
+ let reloadMapStyle = false;
420
+
421
+ if(filters.minDate && filters.minDate !== "") {
422
+ mapFilters.minDate = filters.minDate;
423
+ mapSeqFilters.push([">=", ["get", "date"], filters.minDate]);
424
+ mapPicFilters.push([">=", ["get", "ts"], filters.minDate]);
425
+ }
426
+
427
+ if(filters.maxDate && filters.maxDate !== "") {
428
+ mapFilters.maxDate = filters.maxDate;
429
+ mapSeqFilters.push(["<=", ["get", "date"], filters.maxDate]);
430
+
431
+ // Get tomorrow date for pictures filtering
432
+ // (because ts is date+time, so comparing date only string would fail otherwise)
433
+ let d = new Date(filters.maxDate);
434
+ d.setDate(d.getDate() + 1);
435
+ d = d.toISOString().split("T")[0];
436
+ mapPicFilters.push(["<=", ["get", "ts"], d]);
437
+ }
438
+
439
+ if(filters.pic_type && filters.pic_type !== "") {
440
+ mapFilters.pic_type = filters.pic_type === "flat" ? "flat" : "equirectangular";
441
+ mapSeqFilters.push(["==", ["get", "type"], mapFilters.pic_type]);
442
+ mapPicFilters.push(["==", ["get", "type"], mapFilters.pic_type]);
443
+ }
444
+ if(hasGridStats) {
445
+ reloadMapStyle = true;
446
+ }
447
+
448
+ if(filters.camera && filters.camera !== "") {
449
+ mapFilters.camera = filters.camera;
450
+ // low/high model hack : to enable fuzzy filtering of camera make and model
451
+ const lowModel = filters.camera.toLowerCase().trim() + " ";
452
+ const highModel = filters.camera.toLowerCase().trim() + "zzzzzzzzzzzzzzzzzzzz";
453
+ const collator = ["collator", { "case-sensitive": false, "diacritic-sensitive": false } ];
454
+ mapSeqFilters.push([">=", ["get", "model"], lowModel, collator]);
455
+ mapSeqFilters.push(["<=", ["get", "model"], highModel, collator]);
456
+ mapPicFilters.push([">=", ["get", "model"], lowModel, collator]);
457
+ mapPicFilters.push(["<=", ["get", "model"], highModel, collator]);
458
+ }
459
+
460
+ if(filters.qualityscore && filters.qualityscore.length > 0) {
461
+ mapFilters.qualityscore = filters.qualityscore;
462
+ mapSeqFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", mapFilters.qualityscore]]);
463
+ mapPicFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", mapFilters.qualityscore]]);
464
+ }
465
+
466
+ if(filters.theme && Object.values(MAP_THEMES).includes(filters.theme)) {
467
+ mapFilters.theme = filters.theme;
468
+ reloadMapStyle = true;
469
+ }
470
+
471
+ if(mapSeqFilters.length == 0) { mapSeqFilters = null; }
472
+ else {
473
+ mapSeqFilters.unshift("all");
474
+ }
475
+
476
+ if(mapPicFilters.length == 0) { mapPicFilters = null; }
477
+ else {
478
+ mapPicFilters.unshift("all");
479
+ mapPicFilters = ["step", ["zoom"],
480
+ true,
481
+ TILES_PICTURES_ZOOM, mapPicFilters
482
+ ];
483
+ }
484
+
485
+ return { mapFilters, mapSeqFilters, mapPicFilters, reloadMapStyle };
486
+ }
487
+
365
488
  /**
366
489
  * Get cleaned-up layer ID for a specific user.
367
490
  * @param {string} userId The user UUID (or "geovisio" for general layer)