@panoramax/web-viewer 5.0.0-develop-d7206514 → 5.0.0-develop-d26305dd

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.
Files changed (152) hide show
  1. package/build/bundle.cjs +3399 -0
  2. package/build/bundle.cjs.map +1 -0
  3. package/build/bundle_photoviewer.cjs +2510 -0
  4. package/build/bundle_photoviewer.cjs.map +1 -0
  5. package/build/cjs/index.js +1 -1
  6. package/build/cjs/index_photoviewer.js +1 -1
  7. package/build/components/core/Basic.css +56 -0
  8. package/build/components/core/Basic.js +378 -0
  9. package/build/components/core/CoverageMap.css +10 -0
  10. package/build/components/core/CoverageMap.js +169 -0
  11. package/build/components/core/Editor.css +33 -0
  12. package/build/components/core/Editor.js +398 -0
  13. package/build/components/core/PhotoViewer.css +70 -0
  14. package/build/components/core/PhotoViewer.js +650 -0
  15. package/build/components/core/Viewer.css +130 -0
  16. package/build/components/core/Viewer.js +711 -0
  17. package/build/components/core/index.js +10 -0
  18. package/build/components/index.js +11 -0
  19. package/build/components/index_photoviewer.js +6 -0
  20. package/build/components/layout/BottomDrawer.js +258 -0
  21. package/build/components/layout/CorneredGrid.js +143 -0
  22. package/build/components/layout/Mini.js +121 -0
  23. package/build/components/layout/Tabs.js +140 -0
  24. package/build/components/layout/index.js +9 -0
  25. package/build/components/menus/LocationPrecisionDoc.js +42 -0
  26. package/build/components/menus/MapBackground.js +110 -0
  27. package/build/components/menus/MapFilters.js +567 -0
  28. package/build/components/menus/MapLayers.js +238 -0
  29. package/build/components/menus/MapLegend.js +68 -0
  30. package/build/components/menus/MiniPictureLegend.js +73 -0
  31. package/build/components/menus/PictureLegend.js +379 -0
  32. package/build/components/menus/PictureMetadata.js +380 -0
  33. package/build/components/menus/PlayerOptions.js +93 -0
  34. package/build/components/menus/QualityScoreDoc.js +42 -0
  35. package/build/components/menus/ReportForm.js +132 -0
  36. package/build/components/menus/SemanticsDoc.js +38 -0
  37. package/build/components/menus/SemanticsDownload.js +33 -0
  38. package/build/components/menus/SemanticsFilters.js +153 -0
  39. package/build/components/menus/SemanticsList.js +413 -0
  40. package/build/components/menus/SemanticsMetadata.js +368 -0
  41. package/build/components/menus/Share.js +105 -0
  42. package/build/components/menus/index.js +22 -0
  43. package/build/components/menus/index_photoviewer.js +11 -0
  44. package/build/components/styles.js +557 -0
  45. package/build/components/ui/AnnotationsSwitch.js +159 -0
  46. package/build/components/ui/Button.js +77 -0
  47. package/build/components/ui/ButtonGroup.css +59 -0
  48. package/build/components/ui/ButtonGroup.js +69 -0
  49. package/build/components/ui/CopyButton.js +110 -0
  50. package/build/components/ui/Grade.js +54 -0
  51. package/build/components/ui/GradeFilter.js +122 -0
  52. package/build/components/ui/IconSwitch.js +193 -0
  53. package/build/components/ui/LinkButton.js +67 -0
  54. package/build/components/ui/ListGroup.js +66 -0
  55. package/build/components/ui/ListItem.js +90 -0
  56. package/build/components/ui/Loader.js +203 -0
  57. package/build/components/ui/Map.css +63 -0
  58. package/build/components/ui/Map.js +853 -0
  59. package/build/components/ui/MapMore.js +175 -0
  60. package/build/components/ui/Photo.css +50 -0
  61. package/build/components/ui/Photo.js +1502 -0
  62. package/build/components/ui/Popup.js +145 -0
  63. package/build/components/ui/ProgressBar.js +104 -0
  64. package/build/components/ui/QualityScore.js +147 -0
  65. package/build/components/ui/SearchBar.js +374 -0
  66. package/build/components/ui/SemanticsEditor.js +191 -0
  67. package/build/components/ui/SemanticsTable.js +88 -0
  68. package/build/components/ui/Switch.js +139 -0
  69. package/build/components/ui/TogglableGroup.js +157 -0
  70. package/build/components/ui/index.js +29 -0
  71. package/build/components/ui/index_photoviewer.js +21 -0
  72. package/build/components/ui/widgets/CopyCoordinates.js +75 -0
  73. package/build/components/ui/widgets/GeoSearch.css +21 -0
  74. package/build/components/ui/widgets/GeoSearch.js +150 -0
  75. package/build/components/ui/widgets/Legend.js +190 -0
  76. package/build/components/ui/widgets/LevelSelect.css +51 -0
  77. package/build/components/ui/widgets/LevelSelect.js +143 -0
  78. package/build/components/ui/widgets/MapFiltersButton.js +114 -0
  79. package/build/components/ui/widgets/MapLayersButton.js +79 -0
  80. package/build/components/ui/widgets/OSMEditors.js +155 -0
  81. package/build/components/ui/widgets/PictureLegendActions.js +99 -0
  82. package/build/components/ui/widgets/Player.css +7 -0
  83. package/build/components/ui/widgets/Player.js +154 -0
  84. package/build/components/ui/widgets/SemanticsFiltersButton.js +65 -0
  85. package/build/components/ui/widgets/Zoom.js +84 -0
  86. package/build/components/ui/widgets/index.js +16 -0
  87. package/build/components/ui/widgets/index_photoviewer.js +7 -0
  88. package/build/esm/components/core/Basic.js +1 -1
  89. package/build/img/arrow_360.svg +14 -0
  90. package/build/img/arrow_flat.svg +11 -0
  91. package/build/img/arrow_triangle.svg +9 -0
  92. package/build/img/arrow_turn.svg +8 -0
  93. package/build/img/bg_aerial.jpg +0 -0
  94. package/build/img/bg_streets.jpg +0 -0
  95. package/build/img/loader_base.jpg +0 -0
  96. package/build/img/logo_dead.svg +91 -0
  97. package/build/img/marker.svg +17 -0
  98. package/build/img/marker_blue.svg +20 -0
  99. package/build/img/osm.svg +49 -0
  100. package/build/img/panoramax.svg +13 -0
  101. package/build/img/switch_big.svg +54 -0
  102. package/build/img/switch_mini.svg +48 -0
  103. package/build/img/wd.svg +1 -0
  104. package/build/index_photoviewer.js +4 -0
  105. package/build/package.json +148 -0
  106. package/build/servers.js +14 -0
  107. package/build/translations/ar.json +1 -0
  108. package/build/translations/be.json +257 -0
  109. package/build/translations/br.json +81 -0
  110. package/build/translations/cy.json +117 -0
  111. package/build/translations/da.json +300 -0
  112. package/build/translations/de.json +309 -0
  113. package/build/translations/en.json +294 -0
  114. package/build/translations/eo.json +235 -0
  115. package/build/translations/es.json +292 -0
  116. package/build/translations/fi.json +1 -0
  117. package/build/translations/fr.json +294 -0
  118. package/build/translations/hr.json +294 -0
  119. package/build/translations/hu.json +294 -0
  120. package/build/translations/it.json +306 -0
  121. package/build/translations/ja.json +182 -0
  122. package/build/translations/ko.json +1 -0
  123. package/build/translations/nl.json +305 -0
  124. package/build/translations/nn.json +1 -0
  125. package/build/translations/pl.json +169 -0
  126. package/build/translations/pt.json +296 -0
  127. package/build/translations/pt_BR.json +304 -0
  128. package/build/translations/sv.json +182 -0
  129. package/build/translations/ti.json +9 -0
  130. package/build/translations/tr.json +297 -0
  131. package/build/translations/uk.json +268 -0
  132. package/build/translations/zh_Hant.json +309 -0
  133. package/build/utils/API.js +928 -0
  134. package/build/utils/InitParameters.js +521 -0
  135. package/build/utils/MapStyleComposer.js +889 -0
  136. package/build/utils/PanoraMapProtocol.js +49 -0
  137. package/build/utils/PhotoAdapter.js +49 -0
  138. package/build/utils/PresetsManager.js +148 -0
  139. package/build/utils/SemanticsMapProtocol.js +144 -0
  140. package/build/utils/URLHandler.js +426 -0
  141. package/build/utils/geocoder.js +203 -0
  142. package/build/utils/i18n.js +128 -0
  143. package/build/utils/index.js +17 -0
  144. package/build/utils/index_photoviewer.js +14 -0
  145. package/build/utils/indoor.js +200 -0
  146. package/build/utils/map.js +788 -0
  147. package/build/utils/picture.js +507 -0
  148. package/build/utils/semantics.js +321 -0
  149. package/build/utils/services.js +148 -0
  150. package/build/utils/utils.js +433 -0
  151. package/build/utils/widgets.js +110 -0
  152. package/package.json +1 -1
@@ -0,0 +1,788 @@
1
+ import {
2
+ COLORS, GPS_VALUES, QUALITYSCORE_GPS_VALUES,
3
+ QUALITYSCORE_POND_GPS, QUALITYSCORE_POND_RES, QUALITYSCORE_RES_360_VALUES,
4
+ QUALITYSCORE_RES_FLAT_VALUES, QUALITYSCORE_VALUES, isNullId, svgToImg,
5
+ } from "./utils.js";
6
+
7
+ const LoaderImg = await fetch(new URL("../img/marker.svg", import.meta.url).href).then(res => res.text());
8
+
9
+ export const RASTER_LAYER_ID = "pnx-aerial";
10
+
11
+ export const TILES_PICTURES_ZOOM = 15;
12
+ export const TILES_PICTURES_SYMBOL_ZOOM = 19;
13
+
14
+ const CROWDED_TILE_COND = ["!", ["has", "uncrowded"]];
15
+ const EQUIRECT_COND = ["==", ["get", "type"], "equirectangular"];
16
+
17
+ function getVectorStyles(noCrowded) {
18
+ return {
19
+ PICTURES: {
20
+ "paint": {
21
+ "circle-radius": ["interpolate", ["linear"], ["zoom"],
22
+ TILES_PICTURES_ZOOM, 0,
23
+ TILES_PICTURES_ZOOM+0.5, noCrowded ? 1 : ["case", CROWDED_TILE_COND, 0, 1],
24
+ TILES_PICTURES_ZOOM+1.5, noCrowded ? 2 : ["case", CROWDED_TILE_COND, 0, 2],
25
+ TILES_PICTURES_ZOOM+2, noCrowded ? 4 : ["case", CROWDED_TILE_COND, 1, 4],
26
+ TILES_PICTURES_ZOOM+3, noCrowded ? 5 : ["case", CROWDED_TILE_COND, 3, 5],
27
+ TILES_PICTURES_ZOOM+5, noCrowded ? 7 : ["case", CROWDED_TILE_COND, 5, 7],
28
+ TILES_PICTURES_ZOOM+7, noCrowded ? 7 : ["case", CROWDED_TILE_COND, 7, 7],
29
+ ],
30
+ "circle-opacity": ["interpolate", ["linear"], ["zoom"],
31
+ TILES_PICTURES_ZOOM, 0,
32
+ TILES_PICTURES_ZOOM+0.5, noCrowded ? 1 : ["case", CROWDED_TILE_COND, 0, 1],
33
+ TILES_PICTURES_ZOOM+1.5, noCrowded ? 1 : ["case", CROWDED_TILE_COND, 0, 1],
34
+ TILES_PICTURES_ZOOM+2, 1
35
+ ],
36
+ "circle-stroke-opacity": ["case",
37
+ ["all", CROWDED_TILE_COND, EQUIRECT_COND], 0.2,
38
+ EQUIRECT_COND, 0.4,
39
+ 0
40
+ ],
41
+ "circle-stroke-width": ["interpolate", ["linear"], ["zoom"],
42
+ TILES_PICTURES_ZOOM+2.5, 0,
43
+ TILES_PICTURES_ZOOM+3.5, noCrowded ? ["case",
44
+ EQUIRECT_COND, 10,
45
+ 2
46
+ ] : ["case",
47
+ ["all", CROWDED_TILE_COND, EQUIRECT_COND], 0,
48
+ EQUIRECT_COND, 10,
49
+ CROWDED_TILE_COND, 0,
50
+ 2
51
+ ],
52
+ TILES_PICTURES_ZOOM+4.5, noCrowded ? ["case",
53
+ EQUIRECT_COND, 10,
54
+ 2
55
+ ]: ["case",
56
+ ["all", CROWDED_TILE_COND, EQUIRECT_COND], 10,
57
+ EQUIRECT_COND, 10,
58
+ CROWDED_TILE_COND, 0,
59
+ 2
60
+ ]
61
+ ],
62
+ },
63
+ "layout": {}
64
+ },
65
+ SEQUENCES: {
66
+ "paint": {
67
+ "line-width": ["interpolate", ["linear"], ["zoom"],
68
+ 0, 0.5,
69
+ 10, 1.5,
70
+ TILES_PICTURES_ZOOM-0.5, 1.5,
71
+ TILES_PICTURES_ZOOM+0.5, noCrowded ? 2 : ["case", CROWDED_TILE_COND, 0.5, 2],
72
+ 22, noCrowded ? 4 : ["case", CROWDED_TILE_COND, 1, 4],
73
+ ],
74
+ },
75
+ "layout": {
76
+ "line-cap": "square",
77
+ }
78
+ },
79
+ SEQUENCES_PLUS: {
80
+ "paint": {
81
+ "line-width": ["interpolate", ["linear"], ["zoom"], 0, 15, TILES_PICTURES_ZOOM+1, 30],
82
+ "line-opacity": 0,
83
+ "line-color": "#ff0000",
84
+ },
85
+ "layout": {
86
+ "line-cap": "square",
87
+ }
88
+ },
89
+ GRID: {
90
+ "layout": {
91
+ "circle-sort-key": ["get","coef"]
92
+ },
93
+ "paint": {
94
+ "circle-color": ["interpolate",["linear"],["get","coef"],
95
+ 0, "#FFA726",
96
+ 0.5, "#E65100",
97
+ 1, "#3E2723"
98
+ ],
99
+ "circle-opacity": ["interpolate",["linear"],["zoom"],
100
+ 4, 0.5,
101
+ 5, 1,
102
+ 6.75, 1,
103
+ 7, 0
104
+ ],
105
+ "circle-radius": ["interpolate",["linear"],["zoom"],
106
+ 1, ["match",["get","coef"], 0, 0, 1],
107
+ 5, ["match",["get","coef"], 0, 0, 2.5],
108
+ 7, ["match",["get","coef"], 0, 0, 7]
109
+ ]
110
+ },
111
+ }
112
+ };
113
+ }
114
+
115
+ export const VECTOR_STYLES = getVectorStyles(false);
116
+ export const VECTOR_STYLES_NOCROWD = getVectorStyles(true);
117
+
118
+ // See MapLibre docs for explanation of expressions magic: https://maplibre.org/maplibre-style-spec/expressions/
119
+ const MAP_EXPR_QUALITYSCORE_RES_360 = ["case", ["has", "h_pixel_density"], ["step", ["get", "h_pixel_density"], ...QUALITYSCORE_RES_360_VALUES], 1];
120
+ const MAP_EXPR_QUALITYSCORE_RES_FLAT = ["case", ["has", "h_pixel_density"], ["step", ["get", "h_pixel_density"], ...QUALITYSCORE_RES_FLAT_VALUES], 1];
121
+ const MAP_EXPR_QUALITYSCORE_RES = [
122
+ "case", ["==", ["get", "type"], "equirectangular"],
123
+ MAP_EXPR_QUALITYSCORE_RES_360, MAP_EXPR_QUALITYSCORE_RES_FLAT
124
+ ];
125
+ export const MAP_EXPR_QUALITYSCORE_GPS = ["case", ["has", "gps_accuracy"], ["step", ["get", "gps_accuracy"], ...QUALITYSCORE_GPS_VALUES], 1];
126
+ // Note: score is also calculated in widgets/popup code
127
+ export const MAP_EXPR_QUALITYSCORE = [
128
+ "round",
129
+ ["+",
130
+ ["*", MAP_EXPR_QUALITYSCORE_RES, QUALITYSCORE_POND_RES],
131
+ ["*", MAP_EXPR_QUALITYSCORE_GPS, QUALITYSCORE_POND_GPS]]
132
+ ];
133
+
134
+ // Themes
135
+ const oneDay = 24 * 60 * 60 * 1000;
136
+ const toDate = d => new Date(d).toISOString().split("T")[0];
137
+ const dayNow = Date.now();
138
+ const day1month = toDate(dayNow - 30 * oneDay);
139
+ const day1year = toDate(dayNow - 365 * oneDay);
140
+ const day2years = toDate(dayNow - 2 * 365 * oneDay);
141
+
142
+ export const MAP_THEMES_STYLES = {
143
+ default: {
144
+ id: "default",
145
+ sort: [],
146
+ color: [COLORS.BASE]
147
+ },
148
+ age: {
149
+ id: "age",
150
+ sort: [
151
+ ["!", ["any", ["has", "date"], ["has", "ts"]]], 0,
152
+ [">=", ["coalesce", ["get", "date"], ["get", "ts"]], day1month], 50,
153
+ [">=", ["coalesce", ["get", "date"], ["get", "ts"]], day1year], 49,
154
+ [">=", ["coalesce", ["get", "date"], ["get", "ts"]], day2years], 48,
155
+ ],
156
+ color: [
157
+ ["!", ["any", ["has", "date"], ["has", "ts"]]], COLORS.BASE,
158
+ [">=", ["coalesce", ["get", "date"], ["get", "ts"]], day1month], COLORS.PALETTE_4,
159
+ [">=", ["coalesce", ["get", "date"], ["get", "ts"]], day1year], COLORS.PALETTE_3,
160
+ [">=", ["coalesce", ["get", "date"], ["get", "ts"]], day2years], COLORS.PALETTE_2,
161
+ COLORS.PALETTE_1
162
+ ]
163
+ },
164
+ type: {
165
+ id: "type",
166
+ sort: [
167
+ ["!", ["has", "type"]], 0,
168
+ ["==", ["get", "type"], "equirectangular"], 50,
169
+ ],
170
+ color: [
171
+ ["!", ["has", "type"]], COLORS.BASE,
172
+ ["==", ["get", "type"], "equirectangular"], COLORS.QUALI_1,
173
+ COLORS.QUALI_2
174
+ ]
175
+ },
176
+ score: {
177
+ id: "score",
178
+ sort: [
179
+ ["==", MAP_EXPR_QUALITYSCORE, 5], 80,
180
+ ["==", MAP_EXPR_QUALITYSCORE, 4], 65,
181
+ ["==", MAP_EXPR_QUALITYSCORE, 3], 50,
182
+ ["==", MAP_EXPR_QUALITYSCORE, 2], 35,
183
+ ["==", MAP_EXPR_QUALITYSCORE, 1], 20,
184
+ ],
185
+ color: [
186
+ ["==", MAP_EXPR_QUALITYSCORE, 5], QUALITYSCORE_VALUES[0].color,
187
+ ["==", MAP_EXPR_QUALITYSCORE, 4], QUALITYSCORE_VALUES[1].color,
188
+ ["==", MAP_EXPR_QUALITYSCORE, 3], QUALITYSCORE_VALUES[2].color,
189
+ ["==", MAP_EXPR_QUALITYSCORE, 2], QUALITYSCORE_VALUES[3].color,
190
+ QUALITYSCORE_VALUES[4].color,
191
+ ]
192
+ },
193
+ gps: {
194
+ id: "gps",
195
+ sort: [
196
+ ["==", MAP_EXPR_QUALITYSCORE_GPS, 5], 80,
197
+ ["==", MAP_EXPR_QUALITYSCORE_GPS, 4], 65,
198
+ ["==", MAP_EXPR_QUALITYSCORE_GPS, 3], 50,
199
+ ["==", MAP_EXPR_QUALITYSCORE_GPS, 2], 35,
200
+ ["==", MAP_EXPR_QUALITYSCORE_GPS, 1], 20,
201
+ ],
202
+ color: [
203
+ ["==", MAP_EXPR_QUALITYSCORE_GPS, 5], GPS_VALUES[0].color,
204
+ ["==", MAP_EXPR_QUALITYSCORE_GPS, 4], GPS_VALUES[1].color,
205
+ ["==", MAP_EXPR_QUALITYSCORE_GPS, 3], GPS_VALUES[2].color,
206
+ ["==", MAP_EXPR_QUALITYSCORE_GPS, 2], GPS_VALUES[3].color,
207
+ GPS_VALUES[4].color
208
+ ]
209
+ },
210
+ };
211
+
212
+ /**
213
+ * Get the GIF shown while thumbnail loads
214
+ * @param {object} lang Translations
215
+ * @returns The DOM element for this GIF
216
+ * @private
217
+ */
218
+ export function getThumbGif(lang) {
219
+ const thumbGif = document.createElement("img");
220
+ svgToImg(LoaderImg, thumbGif);
221
+ thumbGif.alt = lang.loading;
222
+ thumbGif.title = lang.loading;
223
+ thumbGif.classList.add("pnx-map-thumb", "pnx-map-thumb-loader");
224
+ return thumbGif;
225
+ }
226
+
227
+ export function isNullCoordinates(c) {
228
+ return (
229
+ c === null
230
+ || c === undefined
231
+ || (Array.isArray(c) && (
232
+ (c.length === 2 && c[0] === 0 && c[1] === 0)
233
+ || c.length < 2
234
+ ))
235
+ );
236
+ }
237
+
238
+ /**
239
+ * Is given layer a label layer.
240
+ *
241
+ * This is useful for inserting new vector layer before labels in MapLibre.
242
+ * @param {object} l The layer to check
243
+ * @returns {boolean} True if it's a label layer
244
+ * @private
245
+ */
246
+ export function isLabelLayer(l) {
247
+ return l.type === "symbol"
248
+ && l?.layout?.["text-field"] !== undefined
249
+ && (l.minzoom === undefined || l.minzoom < 15);
250
+ }
251
+
252
+ /**
253
+ * Does given style contain logged-in grid statistics ?
254
+ * @param {object} style The MapLibre JSON style definition
255
+ * @returns {boolean} True if contains fields for logged-in grid statistics.
256
+ * @private
257
+ */
258
+ export function hasStyleLoggedGridStats(style) {
259
+ const fields = style?.metadata?.["panoramax:fields"] || {};
260
+ return (fields?.grid?.includes("logged_coef_360_pictures")
261
+ && fields?.grid?.includes("logged_coef_flat_pictures")
262
+ && fields?.grid?.includes("logged_coef")) || false;
263
+ }
264
+
265
+ /**
266
+ * Get normalized layers definition for Panoramax data display
267
+ * @param {object} style The MapLibre style JSON
268
+ * @returns {object[]} Complete list of Panoramax layers
269
+ * @private
270
+ */
271
+ export function getPanoramaxLayers(style) {
272
+ const s = Object.keys(style.sources)[0];
273
+ const isVT = style.sources[s].type === "vector";
274
+ const vt = isVT ? VECTOR_STYLES : VECTOR_STYLES_NOCROWD;
275
+
276
+ const layers = [
277
+ // Basic sequences
278
+ {
279
+ "id": `${s}_sequences`,
280
+ "type": "line",
281
+ "source": s,
282
+ "source-layer": isVT ? "sequences" : undefined,
283
+ "layout": {
284
+ ...vt.SEQUENCES.layout
285
+ },
286
+ "paint": {
287
+ ...vt.SEQUENCES.paint,
288
+ "line-color": COLORS.BASE,
289
+ },
290
+ },
291
+
292
+ // Padded sequence (for easier click)
293
+ {
294
+ "id": `${s}_sequences_plus`,
295
+ "type": "line",
296
+ "source": s,
297
+ "source-layer": isVT ? "sequences" : undefined,
298
+ "layout": {
299
+ ...vt.SEQUENCES_PLUS.layout
300
+ },
301
+ "paint": {
302
+ ...vt.SEQUENCES_PLUS.paint
303
+ },
304
+ },
305
+
306
+ // Pictures
307
+ {
308
+ "id": `${s}_pictures`,
309
+ "type": "circle",
310
+ "source": s,
311
+ "source-layer": isVT ? "pictures" : undefined,
312
+ "layout": {
313
+ ...vt.PICTURES.layout
314
+ },
315
+ "paint": {
316
+ ...vt.PICTURES.paint,
317
+ "circle-color": COLORS.BASE,
318
+ },
319
+ }
320
+ ];
321
+
322
+ if(style?.metadata?.["panoramax:fields"]?.grid) {
323
+ layers.push({
324
+ "id": `${s}_grid`,
325
+ "type": "circle",
326
+ "source": s,
327
+ "source-layer": "grid",
328
+ "layout": {
329
+ ...vt.GRID.layout
330
+ },
331
+ "paint": {
332
+ ...vt.GRID.paint
333
+ },
334
+ });
335
+ }
336
+
337
+ return layers;
338
+ }
339
+
340
+ /**
341
+ * Get comprehensive values from map filters form
342
+ * @param {Element} mapFiltersMenu The MapFilters DOM Element
343
+ * @param {Element} mapThemeSelect The Map Theme select DOM Element
344
+ * @param {boolean} hasQualityScore Set to true if vector tiles embed quality score data
345
+ * @return {object} The map filters as JSON
346
+ */
347
+ export function mapFiltersFormValues(mapFiltersMenu, mapThemeSelect, hasQualityScore) {
348
+ const fMinDate = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-date-from");
349
+ const fMaxDate = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-date-end");
350
+ const fTypes = mapFiltersMenu?.shadowRoot.querySelectorAll("input[name='pnx-filter-type']");
351
+ const fPicSource = mapFiltersMenu?.shadowRoot.getElementById("pnx-pictures-source");
352
+
353
+ let type = "";
354
+ for(let fTypeId = 0 ; fTypeId < fTypes.length; fTypeId++) {
355
+ const fType = fTypes[fTypeId];
356
+ if(fType.checked) {
357
+ type = fType.value;
358
+ break;
359
+ }
360
+ }
361
+
362
+ let qualityscore = [];
363
+ let gps = [];
364
+ if(hasQualityScore) {
365
+ // Quality score itself
366
+ const fScore = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-qualityscore");
367
+ qualityscore = (fScore?.grade || "").split(",").map(v => parseInt(v)).filter(v => !isNaN(v));
368
+
369
+ // GPS precision
370
+ const fGPS = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-gps");
371
+ gps = (fGPS?.grade || "").split(",").map(v => parseInt(v)).filter(v => !isNaN(v));
372
+ }
373
+
374
+ return {
375
+ minDate: fMinDate?.value,
376
+ maxDate: fMaxDate?.value,
377
+ pic_type: type,
378
+ theme: mapThemeSelect?.value,
379
+ qualityscore,
380
+ gps,
381
+ source: fPicSource?.checked ? "local" : "metacatalog",
382
+ };
383
+ }
384
+
385
+ /**
386
+ * Transforms map filters into MapLibre-like JSON filters
387
+ * @param {object} filters Filters returned by MapFilters widgets
388
+ * @param {boolean} [hasGridStats=false] Set to true if vector tiles offer grid statistics
389
+ * @param {object} [featuresRestrictions] Rules to restrict display of pictures & sequences
390
+ * @returns {object} Set of variables { mapFilters, mapSeqFilters, mapPicFilters, reloadMapStyle }
391
+ */
392
+ export function mapFiltersToLayersFilters(filters, hasGridStats = false, featuresRestrictions = null) {
393
+ let mapFilters = {};
394
+ let mapSeqFilters = [];
395
+ let mapPicFilters = [];
396
+ let reloadMapStyle = false;
397
+
398
+ if(filters.minDate && filters.minDate !== "") {
399
+ mapFilters.minDate = filters.minDate;
400
+ mapSeqFilters.push([">=", ["get", "date"], filters.minDate]);
401
+ mapPicFilters.push([">=", ["get", "ts"], filters.minDate]);
402
+ }
403
+
404
+ if(filters.maxDate && filters.maxDate !== "") {
405
+ mapFilters.maxDate = filters.maxDate;
406
+ mapSeqFilters.push(["<=", ["get", "date"], filters.maxDate]);
407
+
408
+ // Get tomorrow date for pictures filtering
409
+ // (because ts is date+time, so comparing date only string would fail otherwise)
410
+ let d = new Date(filters.maxDate);
411
+ d.setDate(d.getDate() + 1);
412
+ d = d.toISOString().split("T")[0];
413
+ mapPicFilters.push(["<=", ["get", "ts"], d]);
414
+ }
415
+
416
+ if(filters.pic_type && filters.pic_type !== "") {
417
+ mapFilters.pic_type = filters.pic_type === "flat" ? "flat" : "equirectangular";
418
+ mapSeqFilters.push(["==", ["get", "type"], mapFilters.pic_type]);
419
+ mapPicFilters.push(["==", ["get", "type"], mapFilters.pic_type]);
420
+ }
421
+
422
+ if(hasGridStats) {
423
+ reloadMapStyle = true;
424
+ }
425
+
426
+ if(filters.camera && filters.camera !== "") {
427
+ mapFilters.camera = filters.camera;
428
+ // Low/high model hack : to enable fuzzy filtering of camera make and model
429
+ const lowModel = filters.camera.toLowerCase().trim() + " ";
430
+ const highModel = filters.camera.toLowerCase().trim() + "zzzzzzzzzzzzzzzzzzzz";
431
+ const collator = ["collator", { "case-sensitive": false, "diacritic-sensitive": false } ];
432
+ mapSeqFilters.push([">=", ["get", "model"], lowModel, collator]);
433
+ mapSeqFilters.push(["<=", ["get", "model"], highModel, collator]);
434
+ mapPicFilters.push([">=", ["get", "model"], lowModel, collator]);
435
+ mapPicFilters.push(["<=", ["get", "model"], highModel, collator]);
436
+ }
437
+
438
+ if(filters.qualityscore && filters.qualityscore.length > 0) {
439
+ mapFilters.qualityscore = filters.qualityscore;
440
+ mapSeqFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", mapFilters.qualityscore]]);
441
+ mapPicFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", mapFilters.qualityscore]]);
442
+ }
443
+
444
+ if(filters.gps && filters.gps.length > 0) {
445
+ mapFilters.gps = filters.gps;
446
+ mapSeqFilters.push(["in", MAP_EXPR_QUALITYSCORE_GPS, ["literal", mapFilters.gps]]);
447
+ mapPicFilters.push(["in", MAP_EXPR_QUALITYSCORE_GPS, ["literal", mapFilters.gps]]);
448
+ }
449
+
450
+ if(filters.theme && MAP_THEMES_STYLES[filters.theme]) {
451
+ mapFilters.theme = filters.theme;
452
+ reloadMapStyle = true;
453
+ }
454
+
455
+ if(featuresRestrictions?.pictures && featuresRestrictions.pictures.features?.length >= 0) {
456
+ if(featuresRestrictions.pictures.rule === "keep") {
457
+ mapPicFilters.push(["in", ["get", "id"], ["literal", featuresRestrictions.pictures.features]]);
458
+ }
459
+ else if(featuresRestrictions.pictures.features.length > 0) {
460
+ mapPicFilters.push(["!", ["in", ["get", "id"], ["literal", featuresRestrictions.pictures.features]]]);
461
+ }
462
+ }
463
+
464
+ if(featuresRestrictions?.sequences && featuresRestrictions.sequences.features?.length >= 0) {
465
+ if(featuresRestrictions.sequences.rule === "keep") {
466
+ mapSeqFilters.push(["in", ["get", "id"], ["literal", featuresRestrictions.sequences.features]]);
467
+ }
468
+ else if(featuresRestrictions.sequences.features.length > 0) {
469
+ mapSeqFilters.push(["!", ["in", ["get", "id"], ["literal", featuresRestrictions.sequences.features]]]);
470
+ }
471
+ }
472
+
473
+
474
+ if(mapSeqFilters.length == 0) { mapSeqFilters = null; }
475
+ else {
476
+ mapSeqFilters.unshift("all");
477
+ }
478
+
479
+ if(mapPicFilters.length == 0) { mapPicFilters = null; }
480
+ else {
481
+ mapPicFilters.unshift("all");
482
+ mapPicFilters = ["step", ["zoom"],
483
+ true,
484
+ TILES_PICTURES_ZOOM, mapPicFilters
485
+ ];
486
+ }
487
+
488
+ return { mapFilters, mapSeqFilters, mapPicFilters, reloadMapStyle };
489
+ }
490
+
491
+ /**
492
+ * Is the given Panoramax endpoint showing data of a single user ?
493
+ * @param {string} endpoint The endpoint ID
494
+ * @returns {boolean} True if single user
495
+ */
496
+ export function isPanoramaxEndpointSingleUser(endpoint) {
497
+ return !["default", "metacatalog", "", null, undefined].includes(endpoint);
498
+ }
499
+
500
+ /**
501
+ * Switches used coef value in MapLibre style JSON expression
502
+ * @param {*} expr The MapLibre style expression
503
+ * @param {string} newCoefVal The new coef value to use
504
+ * @returns {*} The switched expression
505
+ * @private
506
+ */
507
+ export function switchCoefValue(expr, newCoefVal) {
508
+ if(Array.isArray(expr)) {
509
+ return expr.map(v => switchCoefValue(v, newCoefVal));
510
+ }
511
+ else if(typeof expr === "object" && expr !== null) {
512
+ const newExpr = {};
513
+ for (const key in expr) {
514
+ newExpr[key] = switchCoefValue(expr[key], newCoefVal);
515
+ }
516
+ return newExpr;
517
+ }
518
+ else if(typeof expr === "string" && (expr.startsWith("coef") || expr.startsWith("logged_coef"))) {
519
+ return newCoefVal;
520
+ }
521
+ return expr;
522
+ }
523
+
524
+ /**
525
+ * Creates links between map and photo elements.
526
+ * This enable interactions like click on map showing picture.
527
+ *
528
+ * @param {CoreView} parent The view containing both Photo and Map elements
529
+ * @private
530
+ */
531
+ export function linkMapAndPhoto(parent) {
532
+ // Switched picture
533
+ const onPicLoad = e => {
534
+ if(isNullId(e.detail.picId)) {
535
+ parent.map.displayPictureMarker();
536
+ if(parent?.isMapWide?.()) {
537
+ parent?.mini?.setAttribute("collapsed", "");
538
+ }
539
+ }
540
+ else {
541
+ parent.map.displayPictureMarker(
542
+ e.detail.lon,
543
+ e.detail.lat,
544
+ parent.psv.getXY().x,
545
+ e.detail.first && !isNullCoordinates(parent._initParams?.getMapPostInit()?.center), // Skip centering if precise coordinates are set from URL
546
+ e.detail.picId
547
+ );
548
+
549
+ if(parent?.isMapWide?.()) {
550
+ parent?.mini?.removeAttribute("collapsed");
551
+ }
552
+ }
553
+ };
554
+ parent.psv.addEventListener("picture-loading", onPicLoad);
555
+ parent.psv.addEventListener("picture-loaded", onPicLoad);
556
+
557
+ // Picture view rotated
558
+ parent.psv.addEventListener("view-rotated", () => {
559
+ let x = parent.psv.getPosition().yaw * (180 / Math.PI);
560
+ x += parent.psv.getPictureOriginalHeading();
561
+ parent.map._picMarker.setRotation(x);
562
+ });
563
+
564
+ // Picture preview
565
+ parent.psv.addEventListener("picture-preview-started", e => {
566
+ // Show marker corresponding to selection
567
+ parent.map._picMarkerPreview
568
+ .setLngLat(e.detail.coordinates)
569
+ .setRotation(e.detail.direction || 0)
570
+ .addTo(parent.map);
571
+ });
572
+
573
+ parent.psv.addEventListener("picture-preview-stopped", () => {
574
+ parent.map._picMarkerPreview.remove();
575
+ });
576
+
577
+ parent.psv.addEventListener("picture-loaded", e => {
578
+ if (parent.isWidthSmall() && parent._picPopup && e.detail.picId == parent._picPopup._picId) {
579
+ parent._picPopup.remove();
580
+ }
581
+ });
582
+
583
+ // Picture click
584
+ parent.map.on("picture-click", e => {
585
+ parent.select(e.seqId, e.picId);
586
+ if(!parent.psv._myVTour.state.currentNode && parent?._setFocus) { parent._setFocus("pic"); }
587
+ });
588
+
589
+ // Sequence click
590
+ parent.map.on("sequence-click", e => {
591
+ (parent.getAPI?.() || parent.api).getPicturesAroundCoordinates(
592
+ e.coordinates.lat,
593
+ e.coordinates.lng,
594
+ 0.01,
595
+ 1,
596
+ e.seqId
597
+ ).then(results => {
598
+ if(results?.features?.length > 0) {
599
+ parent.select(results.features[0]?.collection, results.features[0].id);
600
+ if(!parent.psv.getPictureMetadata() && parent?._setFocus) { parent._setFocus("pic"); }
601
+ }
602
+ });
603
+ });
604
+
605
+ // Focus changes
606
+ if(parent._setFocus) {
607
+ // PSV double click
608
+ parent.psv.addEventListener("dblclick", () => { if(parent.isMapWide()) { parent._setFocus("pic"); } });
609
+
610
+ // Map double-click: unselect if focused, toggle focus if unfocused
611
+ parent.map.on("dblclick", () => {
612
+ if(!parent.isMapWide()) { parent._setFocus("map"); }
613
+ else if(parent.psv.getPicturesNavigation() === "any") { parent.select(); }
614
+ });
615
+ }
616
+ }
617
+
618
+ /**
619
+ * Adds events related to keyboard
620
+ * @private
621
+ */
622
+ export function initMapKeyboardHandler(parent) {
623
+ parent.map.keyboard.keydown = function(e) {
624
+ if (e.altKey || e.ctrlKey || e.metaKey) {return;}
625
+
626
+ // Custom keys
627
+ switch(e.key) {
628
+ case "*":
629
+ case "5":
630
+ parent.moveCenter();
631
+ return;
632
+
633
+ case "PageUp":
634
+ case "9":
635
+ parent.psv.goToNextPicture();
636
+ return;
637
+
638
+ case "PageDown":
639
+ case "3":
640
+ parent.psv.goToPrevPicture();
641
+ return;
642
+
643
+ case "Home":
644
+ case "7":
645
+ e.stopPropagation();
646
+ parent._toggleFocus();
647
+ return;
648
+
649
+ case "End":
650
+ case "1":
651
+ parent.mini.toggleAttribute("collapsed");
652
+ return;
653
+
654
+ case " ":
655
+ case "0":
656
+ parent.psv.toggleSequencePlaying();
657
+ return;
658
+ }
659
+
660
+ let zoomDir = 0;
661
+ let bearingDir = 0;
662
+ let pitchDir = 0;
663
+ let xDir = 0;
664
+ let yDir = 0;
665
+
666
+ switch (e.keyCode) {
667
+ case 61:
668
+ case 107:
669
+ case 171:
670
+ case 187:
671
+ zoomDir = 1;
672
+ break;
673
+
674
+ case 189:
675
+ case 109:
676
+ case 173:
677
+ zoomDir = -1;
678
+ break;
679
+
680
+ case 37:
681
+ case 100:
682
+ if (e.shiftKey) {
683
+ bearingDir = -1;
684
+ } else {
685
+ e.preventDefault();
686
+ xDir = -1;
687
+ }
688
+ break;
689
+
690
+ case 39:
691
+ case 102:
692
+ if (e.shiftKey) {
693
+ bearingDir = 1;
694
+ } else {
695
+ e.preventDefault();
696
+ xDir = 1;
697
+ }
698
+ break;
699
+
700
+ case 38:
701
+ case 104:
702
+ if (e.shiftKey) {
703
+ pitchDir = 1;
704
+ } else {
705
+ e.preventDefault();
706
+ yDir = -1;
707
+ }
708
+ break;
709
+
710
+ case 40:
711
+ case 98:
712
+ if (e.shiftKey) {
713
+ pitchDir = -1;
714
+ } else {
715
+ e.preventDefault();
716
+ yDir = 1;
717
+ }
718
+ break;
719
+
720
+ default:
721
+ return;
722
+ }
723
+
724
+ if (this._rotationDisabled) {
725
+ bearingDir = 0;
726
+ pitchDir = 0;
727
+ }
728
+
729
+ return {
730
+ cameraAnimation: (map) => {
731
+ const tr = this._tr;
732
+ map.easeTo({
733
+ duration: 300,
734
+ easeId: "keyboardHandler",
735
+ easing: t => t * (2-t),
736
+ zoom: zoomDir ? Math.round(tr.zoom) + zoomDir * (e.shiftKey ? 2 : 1) : tr.zoom,
737
+ bearing: tr.bearing + bearingDir * this._bearingStep,
738
+ pitch: tr.pitch + pitchDir * this._pitchStep,
739
+ offset: [-xDir * this._panStep, -yDir * this._panStep],
740
+ center: tr.center
741
+ }, {originalEvent: e});
742
+ }
743
+ };
744
+ }.bind(parent.map.keyboard);
745
+ }
746
+
747
+ const MAP_PARAMS_STORAGE = "pnx-map-parameters";
748
+
749
+ /**
750
+ * Reads map parameters from localStorage
751
+ * @private
752
+ */
753
+ export function getMapParamsFromLocalStorage() {
754
+ const params = localStorage.getItem(MAP_PARAMS_STORAGE);
755
+ if(!params) { return {}; }
756
+ try {
757
+ return JSON.parse(params);
758
+ }
759
+ catch(e) {
760
+ console.warn("Can't read map parameters stored in localStorage", e);
761
+ return {};
762
+ }
763
+ }
764
+
765
+ /**
766
+ * Save map parameters into localStorage.
767
+ * @private
768
+ */
769
+ export function saveMapParamsToLocalStorage(map) {
770
+ // Save map state in localStorage
771
+ const save = () => localStorage.setItem(MAP_PARAMS_STORAGE, JSON.stringify({
772
+ center: Object.fromEntries(Object.entries(map.getCenter()).map(([k,v]) => ([k,v.toFixed(7)]))),
773
+ zoom: map.getZoom().toFixed(1),
774
+ background: map._parent.mapStyleComposer.basemap,
775
+ theme: map._parent.mapStyleComposer.panoramaxTheme,
776
+ endpoint: map._parent.mapStyleComposer.panoramax,
777
+ }));
778
+
779
+ // Add events to know when to rewrite info
780
+ map._parent.mapStyleComposer.addEventListener("filters-changed", save);
781
+ map._parent.mapStyleComposer.addEventListener("theme-changed", save);
782
+ map._parent.mapStyleComposer.addEventListener("basemap-changed", save);
783
+ map._parent.mapStyleComposer.addEventListener("panoramax-changed", save);
784
+ map.on("moveend", save);
785
+ map.on("zoomend", save);
786
+ map.on("dragend", save);
787
+ map.on("boxzoomend", save);
788
+ }