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

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 (153) hide show
  1. package/build/cjs/index.js +1 -1
  2. package/build/cjs/index_photoviewer.js +1 -1
  3. package/build/esm/components/core/Basic.js +1 -1
  4. package/build/esm/translations/el.json +92 -1
  5. package/package.json +1 -1
  6. package/build/bundle.cjs +0 -3399
  7. package/build/bundle.cjs.map +0 -1
  8. package/build/bundle_photoviewer.cjs +0 -2510
  9. package/build/bundle_photoviewer.cjs.map +0 -1
  10. package/build/components/core/Basic.css +0 -56
  11. package/build/components/core/Basic.js +0 -378
  12. package/build/components/core/CoverageMap.css +0 -10
  13. package/build/components/core/CoverageMap.js +0 -169
  14. package/build/components/core/Editor.css +0 -33
  15. package/build/components/core/Editor.js +0 -398
  16. package/build/components/core/PhotoViewer.css +0 -70
  17. package/build/components/core/PhotoViewer.js +0 -650
  18. package/build/components/core/Viewer.css +0 -130
  19. package/build/components/core/Viewer.js +0 -711
  20. package/build/components/core/index.js +0 -10
  21. package/build/components/index.js +0 -11
  22. package/build/components/index_photoviewer.js +0 -6
  23. package/build/components/layout/BottomDrawer.js +0 -258
  24. package/build/components/layout/CorneredGrid.js +0 -143
  25. package/build/components/layout/Mini.js +0 -121
  26. package/build/components/layout/Tabs.js +0 -140
  27. package/build/components/layout/index.js +0 -9
  28. package/build/components/menus/LocationPrecisionDoc.js +0 -42
  29. package/build/components/menus/MapBackground.js +0 -110
  30. package/build/components/menus/MapFilters.js +0 -567
  31. package/build/components/menus/MapLayers.js +0 -238
  32. package/build/components/menus/MapLegend.js +0 -68
  33. package/build/components/menus/MiniPictureLegend.js +0 -73
  34. package/build/components/menus/PictureLegend.js +0 -379
  35. package/build/components/menus/PictureMetadata.js +0 -380
  36. package/build/components/menus/PlayerOptions.js +0 -93
  37. package/build/components/menus/QualityScoreDoc.js +0 -42
  38. package/build/components/menus/ReportForm.js +0 -132
  39. package/build/components/menus/SemanticsDoc.js +0 -38
  40. package/build/components/menus/SemanticsDownload.js +0 -33
  41. package/build/components/menus/SemanticsFilters.js +0 -153
  42. package/build/components/menus/SemanticsList.js +0 -413
  43. package/build/components/menus/SemanticsMetadata.js +0 -368
  44. package/build/components/menus/Share.js +0 -105
  45. package/build/components/menus/index.js +0 -22
  46. package/build/components/menus/index_photoviewer.js +0 -11
  47. package/build/components/styles.js +0 -557
  48. package/build/components/ui/AnnotationsSwitch.js +0 -159
  49. package/build/components/ui/Button.js +0 -77
  50. package/build/components/ui/ButtonGroup.css +0 -59
  51. package/build/components/ui/ButtonGroup.js +0 -69
  52. package/build/components/ui/CopyButton.js +0 -110
  53. package/build/components/ui/Grade.js +0 -54
  54. package/build/components/ui/GradeFilter.js +0 -122
  55. package/build/components/ui/IconSwitch.js +0 -193
  56. package/build/components/ui/LinkButton.js +0 -67
  57. package/build/components/ui/ListGroup.js +0 -66
  58. package/build/components/ui/ListItem.js +0 -90
  59. package/build/components/ui/Loader.js +0 -203
  60. package/build/components/ui/Map.css +0 -63
  61. package/build/components/ui/Map.js +0 -853
  62. package/build/components/ui/MapMore.js +0 -175
  63. package/build/components/ui/Photo.css +0 -50
  64. package/build/components/ui/Photo.js +0 -1502
  65. package/build/components/ui/Popup.js +0 -145
  66. package/build/components/ui/ProgressBar.js +0 -104
  67. package/build/components/ui/QualityScore.js +0 -147
  68. package/build/components/ui/SearchBar.js +0 -374
  69. package/build/components/ui/SemanticsEditor.js +0 -191
  70. package/build/components/ui/SemanticsTable.js +0 -88
  71. package/build/components/ui/Switch.js +0 -139
  72. package/build/components/ui/TogglableGroup.js +0 -157
  73. package/build/components/ui/index.js +0 -29
  74. package/build/components/ui/index_photoviewer.js +0 -21
  75. package/build/components/ui/widgets/CopyCoordinates.js +0 -75
  76. package/build/components/ui/widgets/GeoSearch.css +0 -21
  77. package/build/components/ui/widgets/GeoSearch.js +0 -150
  78. package/build/components/ui/widgets/Legend.js +0 -190
  79. package/build/components/ui/widgets/LevelSelect.css +0 -51
  80. package/build/components/ui/widgets/LevelSelect.js +0 -143
  81. package/build/components/ui/widgets/MapFiltersButton.js +0 -114
  82. package/build/components/ui/widgets/MapLayersButton.js +0 -79
  83. package/build/components/ui/widgets/OSMEditors.js +0 -155
  84. package/build/components/ui/widgets/PictureLegendActions.js +0 -99
  85. package/build/components/ui/widgets/Player.css +0 -7
  86. package/build/components/ui/widgets/Player.js +0 -154
  87. package/build/components/ui/widgets/SemanticsFiltersButton.js +0 -65
  88. package/build/components/ui/widgets/Zoom.js +0 -84
  89. package/build/components/ui/widgets/index.js +0 -16
  90. package/build/components/ui/widgets/index_photoviewer.js +0 -7
  91. package/build/img/arrow_360.svg +0 -14
  92. package/build/img/arrow_flat.svg +0 -11
  93. package/build/img/arrow_triangle.svg +0 -9
  94. package/build/img/arrow_turn.svg +0 -8
  95. package/build/img/bg_aerial.jpg +0 -0
  96. package/build/img/bg_streets.jpg +0 -0
  97. package/build/img/loader_base.jpg +0 -0
  98. package/build/img/logo_dead.svg +0 -91
  99. package/build/img/marker.svg +0 -17
  100. package/build/img/marker_blue.svg +0 -20
  101. package/build/img/osm.svg +0 -49
  102. package/build/img/panoramax.svg +0 -13
  103. package/build/img/switch_big.svg +0 -54
  104. package/build/img/switch_mini.svg +0 -48
  105. package/build/img/wd.svg +0 -1
  106. package/build/index_photoviewer.js +0 -4
  107. package/build/package.json +0 -148
  108. package/build/servers.js +0 -14
  109. package/build/translations/ar.json +0 -1
  110. package/build/translations/be.json +0 -257
  111. package/build/translations/br.json +0 -81
  112. package/build/translations/cy.json +0 -117
  113. package/build/translations/da.json +0 -300
  114. package/build/translations/de.json +0 -309
  115. package/build/translations/en.json +0 -294
  116. package/build/translations/eo.json +0 -235
  117. package/build/translations/es.json +0 -292
  118. package/build/translations/fi.json +0 -1
  119. package/build/translations/fr.json +0 -294
  120. package/build/translations/hr.json +0 -294
  121. package/build/translations/hu.json +0 -294
  122. package/build/translations/it.json +0 -306
  123. package/build/translations/ja.json +0 -182
  124. package/build/translations/ko.json +0 -1
  125. package/build/translations/nl.json +0 -305
  126. package/build/translations/nn.json +0 -1
  127. package/build/translations/pl.json +0 -169
  128. package/build/translations/pt.json +0 -296
  129. package/build/translations/pt_BR.json +0 -304
  130. package/build/translations/sv.json +0 -182
  131. package/build/translations/ti.json +0 -9
  132. package/build/translations/tr.json +0 -297
  133. package/build/translations/uk.json +0 -268
  134. package/build/translations/zh_Hant.json +0 -309
  135. package/build/utils/API.js +0 -928
  136. package/build/utils/InitParameters.js +0 -521
  137. package/build/utils/MapStyleComposer.js +0 -889
  138. package/build/utils/PanoraMapProtocol.js +0 -49
  139. package/build/utils/PhotoAdapter.js +0 -49
  140. package/build/utils/PresetsManager.js +0 -148
  141. package/build/utils/SemanticsMapProtocol.js +0 -144
  142. package/build/utils/URLHandler.js +0 -426
  143. package/build/utils/geocoder.js +0 -203
  144. package/build/utils/i18n.js +0 -128
  145. package/build/utils/index.js +0 -17
  146. package/build/utils/index_photoviewer.js +0 -14
  147. package/build/utils/indoor.js +0 -200
  148. package/build/utils/map.js +0 -788
  149. package/build/utils/picture.js +0 -507
  150. package/build/utils/semantics.js +0 -321
  151. package/build/utils/services.js +0 -148
  152. package/build/utils/utils.js +0 -433
  153. package/build/utils/widgets.js +0 -110
@@ -1,889 +0,0 @@
1
- import {
2
- MAP_EXPR_QUALITYSCORE, MAP_EXPR_QUALITYSCORE_GPS, MAP_THEMES_STYLES, RASTER_LAYER_ID,
3
- TILES_PICTURES_ZOOM, getPanoramaxLayers, hasStyleLoggedGridStats,
4
- isLabelLayer, isPanoramaxEndpointSingleUser, switchCoefValue,
5
- } from "./map.js";
6
- import { autoDetectLocale } from "./i18n.js";
7
- import { COLORS, getUserAccount } from "./utils.js";
8
-
9
- export const BASEMAPS_DEFAULT_IDS = ["streets", "aerial"];
10
-
11
- /**
12
- * Map Style Composer allows an easy management of MapLibre styles.
13
- * As Panoramax Viewer composes with many sources and layers, it's helpful.
14
- * There are several "Layer Ranges" (from bottom to top):
15
- * - Basemap: contextual background, either raster of vector
16
- * - Data Underlay: vector data below Panoramax data (indoor maps)
17
- * - Panoramax: pictures, sequences, grid statistics
18
- * - Data Overlay: vector data on top of Panoramax data (traffic signs...)
19
- * The composer offers helpers to switch between any layer in a specific range.
20
- * @class Panoramax.utils.MapStyleComposer
21
- * @param {Panoramax.components.core.basic} parent The parent view
22
- * @property {string} basemap The selected basemap
23
- * @property {Set} dataUnderlays The visible data underlays
24
- * @property {string} panoramax The selected Panoramax endpoint
25
- * @property {Set} dataOverlays The visible data overlays
26
- * @fires Panoramax.utils.MapStyleComposer#basemap-added
27
- * @fires Panoramax.utils.MapStyleComposer#basemap-changed
28
- * @fires Panoramax.utils.MapStyleComposer#dataunderlay-added
29
- * @fires Panoramax.utils.MapStyleComposer#dataunderlay-changed
30
- * @fires Panoramax.utils.MapStyleComposer#panoramax-added
31
- * @fires Panoramax.utils.MapStyleComposer#panoramax-changed
32
- * @fires Panoramax.utils.MapStyleComposer#theme-changed
33
- * @fires Panoramax.utils.MapStyleComposer#filters-changed
34
- * @fires Panoramax.utils.MapStyleComposer#dataoverlay-added
35
- * @fires Panoramax.utils.MapStyleComposer#dataoverlay-changed
36
- * @example
37
- * const msc = new Panoramax.utils.MapStyleComposer(viewer)
38
- */
39
- export default class MapStyleComposer extends EventTarget {
40
- constructor(parent) {
41
- super();
42
-
43
- this._parent = parent;
44
- this.map = null;
45
- this._mapUpdateDebounce = null;
46
-
47
- // Layers range storage
48
- this.layerRanges = {
49
- basemaps: {},
50
- dataUnderlays: {},
51
- panoramax: {},
52
- dataOverlays: {}
53
- };
54
-
55
- // Selected/visible layers
56
- this.basemap = null;
57
- this.dataUnderlays = new Set();
58
- this.panoramax = null;
59
- this.dataOverlays = new Set();
60
-
61
- // Detached sources = for layers not passing through MapStyleComposer
62
- this.detachedSources = {};
63
-
64
- // Panoramax themes & filters
65
- this.panoramaxTheme = MAP_THEMES_STYLES.default.id;
66
- this.panoramaxFilters = {};
67
- }
68
-
69
- /**
70
- * Gives MapLibre JSON style to pass to Map constructor or setStyle function.
71
- * This gives ready-to-use sources and layers.
72
- * @returns {object} The MapLibre style object
73
- * @memberof Panoramax.utils.MapStyleComposer#
74
- */
75
- getMapStyle() {
76
- let sources = {};
77
- let layers = [];
78
- let labelLayers = [];
79
- let metadata = {};
80
- let sprite = [];
81
-
82
- // Helper to register layers split into classic/labels
83
- const preserveFilters = ll => {
84
- if(this.map) {
85
- (ll || [])
86
- .filter(l => this.map.getLayer(l.id))
87
- .forEach(l => {
88
- const f = this.map.getFilter(l.id);
89
- if(f) { l.filter = f; }
90
- });
91
- }
92
- return ll;
93
- };
94
- const addLayers = ll => {
95
- (ll || []).forEach(l => {
96
- if(isLabelLayer(l)) { labelLayers.push(l); }
97
- else { layers.push(l); }
98
- });
99
- };
100
- const addSprite = (s, id) => {
101
- if(Array.isArray(s)) {
102
- sprite = sprite.concat(s);
103
- }
104
- // eslint-disable-next-line eqeqeq
105
- else if(typeof s === "object" && s != null) {
106
- sprite.push(s);
107
- }
108
- else if(typeof s === "string") {
109
- sprite.push({id, url: s});
110
- }
111
- };
112
-
113
- // Detached sources
114
- Object
115
- .entries(this.detachedSources)
116
- .forEach(([sid,s]) => sources[sid] = s);
117
-
118
- // Basemap
119
- if(this.basemap && this.layerRanges.basemaps[this.basemap]) {
120
- sources = Object.assign(sources, this.layerRanges.basemaps[this.basemap].sources);
121
- addLayers(this.layerRanges.basemaps[this.basemap].layers);
122
- metadata = this.layerRanges.basemaps[this.basemap].metadata || {};
123
- addSprite(this.layerRanges.basemaps[this.basemap].sprite, this.basemap);
124
- if(!metadata?.["panoramax:locales"]) {
125
- metadata["panoramax:locales"] = ["fr", "en", "de", "es", "ru", "pt", "zh", "hi", "latin"];
126
- }
127
- }
128
-
129
- // Data underlays
130
- [...this.dataUnderlays].forEach(did => {
131
- if(this.layerRanges.dataUnderlays[did]) {
132
- sources = Object.assign(sources, this.layerRanges.dataUnderlays[did].sources);
133
- addLayers(preserveFilters(this.layerRanges.dataUnderlays[did].layers));
134
- addSprite(this.layerRanges.dataUnderlays[did].sprite, did);
135
- }
136
- });
137
-
138
- // Panoramax vector tile endpoint
139
- if(this.panoramax && this.layerRanges.panoramax[this.panoramax]) {
140
- metadata = Object.assign(metadata, this.layerRanges.panoramax[this.panoramax].metadata);
141
- sources = Object.assign(sources, this.layerRanges.panoramax[this.panoramax].sources);
142
- addSprite(this.layerRanges.panoramax[this.panoramax].sprite, this.panoramax);
143
- let pnxLayers = this._applyPanoramaxThemeOnLayers(this.layerRanges.panoramax[this.panoramax]);
144
- pnxLayers = this._applyPanoramaxFiltersOnLayers(pnxLayers);
145
- addLayers(pnxLayers);
146
- }
147
-
148
- // Data overlays
149
- [...this.dataOverlays].forEach(did => {
150
- if(this.layerRanges.dataOverlays[did]) {
151
- sources = Object.assign(sources, this.layerRanges.dataOverlays[did].sources);
152
- addLayers(preserveFilters(this.layerRanges.dataOverlays[did].layers));
153
- addSprite(this.layerRanges.dataOverlays[did].sprite, did);
154
- }
155
- });
156
-
157
- // Override label layers to use preferred language
158
- if(metadata["panoramax:locales"]) {
159
- let prefLang = this._parent.lang || autoDetectLocale(metadata["panoramax:locales"], "latin");
160
- if(prefLang.includes("-")) { prefLang = prefLang.split("-")[0]; }
161
- if(prefLang.includes("_")) { prefLang = prefLang.split("_")[0]; }
162
- labelLayers.forEach(l => {
163
- if(l.layout["text-field"].includes("name:latin")) {
164
- l.layout["text-field"] = [
165
- "coalesce",
166
- ["get", `name:${prefLang}`],
167
- ["get", "name:latin"],
168
- ["get", "name"]
169
- ];
170
- }
171
- });
172
- }
173
-
174
- /*
175
- * Various fixes for background providers
176
- */
177
-
178
- // OSMFR PMTiles (capital cities)
179
- const citiesLayer = labelLayers.find(l => l.id === "place_label_city");
180
- let capitalLayer = labelLayers.find(l => l.id === "place_label_capital");
181
- if(citiesLayer && !capitalLayer) {
182
- // Create capital layer from original city style
183
- citiesLayer.paint = {
184
- "text-color": "hsl(0, 0%, 0%)",
185
- "text-halo-blur": 0,
186
- "text-halo-color": "hsla(0, 0%, 100%, 1)",
187
- "text-halo-width": 3,
188
- };
189
- citiesLayer.layout["text-letter-spacing"] = 0.1;
190
- capitalLayer = JSON.parse(JSON.stringify(citiesLayer));
191
- capitalLayer.id = "place_label_capital";
192
- capitalLayer.filter = capitalLayer.filter.filter(f => !(f[0] === ">" && f[1] === "capital"));
193
- capitalLayer.filter.push(["<=", "capital", 2]);
194
-
195
- // Edit original city to make it less important
196
- if(!citiesLayer.filter.find(f => f[0] === ">" && f[1] === "capital")) {
197
- citiesLayer.filter.push([">", "capital", 2]);
198
- }
199
- labelLayers.push(capitalLayer);
200
- }
201
-
202
- // IGN FR Maxzoom
203
- if(sources.plan_ign) { sources.plan_ign.maxzoom = 18;}
204
-
205
- // OpenMapTiles styles
206
- if(sources.openmaptiles && !(sources.openmaptiles.attribution || "").includes("OpenMapTiles")) {
207
- sources.openmaptiles.attribution += " | © <a href='https://openmaptiles.org/'>OpenMapTiles</a>";
208
- }
209
-
210
- return {
211
- version: 8,
212
- name: "Panoramax Map Style",
213
- sources,
214
- layers: layers.concat(labelLayers).filter(l => l.id),
215
- metadata,
216
- sprite: sprite.length > 0 ? sprite : undefined,
217
- };
218
- }
219
-
220
- /**
221
- * Throttled function to force map data refresh.
222
- * This allows to make actual effect of layers adds/switches.
223
- * @param {boolean} [reloadSem=false] Force reload of semantics layers (in case of API switch)
224
- * @memberof Panoramax.utils.MapStyleComposer#
225
- */
226
- updateMapStyle(reloadSem = false) {
227
- clearTimeout(this._mapUpdateDebounce);
228
- this._mapUpdateDebounce = setTimeout(() => {
229
- this.map?.setStyle(this.getMapStyle(), {diff: true});
230
-
231
- if(reloadSem) {
232
- this.map._semMapProtocol.clearCache();
233
- Object
234
- .entries(this.map?.getStyle()?.sources || {})
235
- .filter(([,v]) => v.tiles && v.tiles[0].startsWith("sem"))
236
- .forEach(([k,]) => {
237
- const s = this.map.getSource(k);
238
- s.setTiles(s.tiles);
239
- });
240
- }
241
- }, 10);
242
- }
243
-
244
- /**
245
- * Checks if given source already exists in composer, or add it as detached to wait.
246
- * This is useful for sources added directly to MapLibre not through the MapStyleComposer.
247
- * @param {string} id The source ID
248
- * @param {object} source The MapLibre source definition
249
- * @memberof Panoramax.utils.MapStyleComposer#
250
- */
251
- registerSource(id, source) {
252
- let found = false;
253
- const lrids = Object.keys(this.layerRanges);
254
- for(let i=0; i < lrids.length; i++) {
255
- const lr = this.layerRanges[lrids[i]];
256
- const styles = Object.keys(lr);
257
- for(let j=0; j < styles.length; j++) {
258
- const styleId = styles[j];
259
- if(styleId === id) {
260
- found = true;
261
- break;
262
- }
263
- }
264
- if(found) { break; }
265
- }
266
-
267
- if(!found) {
268
- // Shorten indoor= attribution
269
- if(id === "indoorequal") {
270
- source.attribution = "<a href=\"https://indoorequal.org/\" target=\"_blank\">© indoor=</a>";
271
- }
272
-
273
- this.detachedSources[id] = source;
274
- }
275
- }
276
-
277
- /**
278
- * Checks if given layer already exists in composer, or add it as data under/overlay.
279
- * This is useful for layers added directly to MapLibre not through the MapStyleComposer.
280
- * @param {object} layer The MapLibre layer definition
281
- * @param {string} [beforeId] The ID of an existing layer to put this one before
282
- * @memberof Panoramax.utils.MapStyleComposer#
283
- */
284
- registerLayer(layer, beforeId) {
285
- let layerRange = "dataOverlays";
286
- if(layer.source === "indoorequal" || beforeId) {
287
- layerRange = "dataUnderlays";
288
- }
289
-
290
- // Skip indoorequal heatmap if disabled
291
- if(layer.source === "indoorequal" && layer.id === "indoor-heat" && this._parent?._initParams?.getMapInit()?.indoor?.heatmap === false) {
292
- return;
293
- }
294
-
295
- // Look for source in existing layers for appropriate layerRange
296
- let found = false;
297
- Object
298
- .values(this.layerRanges[layerRange])
299
- .forEach(s => {
300
- if(s.sources[layer.source]) {
301
- if(!s.layers) { s.layers = []; }
302
- s.layers.push(layer);
303
- found = true;
304
- }
305
- });
306
-
307
- if(found) { return; }
308
-
309
- // If none found, add it
310
- let style = { sources: {}, layers: [layer] };
311
- if(this.detachedSources[layer.source]) {
312
- style.sources[layer.source] = this.detachedSources[layer.source];
313
- delete this.detachedSources[layer.source];
314
-
315
- if(layerRange === "dataOverlays") {
316
- this.addDataOverlay(layer.id, style, true);
317
- }
318
- else if(layerRange === "dataUnderlays") {
319
- this.addDataUnderlay(layer.id, style, true);
320
- }
321
- }
322
- }
323
-
324
- /**
325
- * Wait for some layer to be available
326
- * @param {string} type The kind of layer (panoramax, dataOverlays, dataUnderlays, basemaps)
327
- * @param {string} id The ID of layer to wait for
328
- * @returns {Promise} Resolves when layer is ready
329
- * @memberof Panoramax.utils.MapStyleComposer#
330
- */
331
- async waitFor(type, id) {
332
- if(this.layerRanges[type][id]) {
333
- return await Promise.resolve();
334
- }
335
- else {
336
- return await new Promise(resolve => setTimeout(resolve, 250)).then(() => this.waitFor(type, id));
337
- }
338
- }
339
-
340
- /**
341
- * Creates a new basemap
342
- * @param {string} id Identifier for this basemap
343
- * @param {object} style The MapLibre-like style definition (object with sources and layers)
344
- * @param {boolean} [switchOn=false] Immediate map display ?
345
- * @memberof Panoramax.utils.MapStyleComposer#
346
- */
347
- async addBasemap(id, style, switchOn = false) {
348
- // Load from string URL
349
- if(typeof style === "string") { style = await fetch(style).then(res => res.json()); }
350
-
351
- this.layerRanges.basemaps[id] = style;
352
-
353
- /**
354
- * Event for basemap added
355
- * @event Panoramax.utils.MapStyleComposer#basemap-added
356
- * @type {CustomEvent}
357
- * @property {string} [detail.basemap] The added basemap ID
358
- */
359
- this.dispatchEvent(new CustomEvent("basemap-added", { detail: { basemap: id }}));
360
-
361
- if(switchOn) { this.switchBasemap(id); }
362
- }
363
-
364
- /**
365
- * Makes this basemap now visible, instead of current one.
366
- * @param {string} id The basemap ID
367
- * @memberof Panoramax.utils.MapStyleComposer#
368
- */
369
- async switchBasemap(id) {
370
- if(id && !this.layerRanges.basemaps[id]) {
371
- await this.waitFor("basemaps", id);
372
- }
373
- if(this.basemap === id || !id) { return; }
374
- this.basemap = id;
375
- this.updateMapStyle();
376
-
377
- /**
378
- * Event for basemap changes
379
- * @event Panoramax.utils.MapStyleComposer#basemap-changed
380
- * @type {CustomEvent}
381
- * @property {string} [detail.basemap] The basemap ID
382
- */
383
- this.dispatchEvent(new CustomEvent("basemap-changed", { detail: { basemap: id } }));
384
- }
385
-
386
- /**
387
- * Does this style contain many different basemaps ?
388
- * @returns {boolean} True if 2 or more basemaps are available
389
- * @memberof Panoramax.utils.MapStyleComposer#
390
- */
391
- hasManyBasemaps() {
392
- return Object.keys(this.layerRanges.basemaps).length >= 2;
393
- }
394
-
395
- /**
396
- * Does this style contains two main basemaps "aerial" & "streets" ?
397
- * @returns {boolean} True if two main basemaps available
398
- * @memberof Panoramax.utils.MapStyleComposer#
399
- */
400
- hasStreetsAerialBasemaps() {
401
- // eslint-disable-next-line eqeqeq
402
- return this.layerRanges.basemaps.aerial != null && this.layerRanges.basemaps.streets != null;
403
- }
404
-
405
- /**
406
- * Does this style contains any other basemaps than two main ones (aerial/streets)
407
- * @returns {boolean} True if other basemaps available
408
- * @memberof Panoramax.utils.MapStyleComposer#
409
- */
410
- hasComplementaryBasemaps() {
411
- // eslint-disable-next-line eqeqeq
412
- return Object.keys(this.layerRanges.basemaps).find(b => !BASEMAPS_DEFAULT_IDS.includes(b)) != null;
413
- }
414
-
415
- /**
416
- * Creates a new data underlay
417
- * @param {string} id Identifier for this basemap
418
- * @param {object} style The MapLibre-like style definition (object with sources and layers)
419
- * @param {boolean} [switchOn=false] Immediate map display ?
420
- * @memberof Panoramax.utils.MapStyleComposer#
421
- */
422
- addDataUnderlay(id, style, switchOn = false) {
423
- this.layerRanges.dataUnderlays[id] = style;
424
-
425
- /**
426
- * Event for data underlay added
427
- * @event Panoramax.utils.MapStyleComposer#dataunderlay-added
428
- * @type {CustomEvent}
429
- * @property {string} [detail.dataUnderlay] The added data underlay ID
430
- */
431
- this.dispatchEvent(new CustomEvent("dataunderlay-added", { detail: { dataUnderlay: id }}));
432
-
433
- if(switchOn) { this.switchDataUnderlayVisibility(id, true); }
434
- }
435
-
436
- /**
437
- * Change visibility of a given data underlay.
438
- * @param {string} id The Identifier of data underlay to change
439
- * @param {boolean} visible Set to true to make visible, false to hide
440
- * @memberof Panoramax.utils.MapStyleComposer#
441
- */
442
- async switchDataUnderlayVisibility(id, visible) {
443
- if(id && !this.layerRanges.dataUnderlays[id]) {
444
- await this.waitFor("dataUnderlays", id);
445
- }
446
-
447
- if(visible) { this.dataUnderlays.add(id); }
448
- else { this.dataUnderlays.delete(id); }
449
- this.updateMapStyle();
450
-
451
- /**
452
- * Event for data underlays changes
453
- * @event Panoramax.utils.MapStyleComposer#dataunderlay-changed
454
- * @type {CustomEvent}
455
- * @property {string} [detail.dataUnderlay] The data underlay ID
456
- * @property {boolean} [detail.visible] True if visible
457
- */
458
- this.dispatchEvent(new CustomEvent("dataunderlay-changed", { detail: { dataUnderlay: id , visible } }));
459
- }
460
-
461
- /**
462
- * Creates a new Panoramax Vector Tile endpoint.
463
- * @param {string} id The identifier for this endpoint
464
- * @param {object} style The MapLibre Style JSON object
465
- * @param {boolean} [switchOn=false] Immediate map display ?
466
- * @memberof Panoramax.utils.MapStyleComposer#
467
- */
468
- addPanoramaxEndpoint(id, style, switchOn = false) {
469
- // Override tiles protocol (for tile crowdiness)
470
- Object
471
- .values(style?.sources || {})
472
- .filter(s => s.tiles)
473
- .forEach(s => s.tiles = s.tiles.map(t => t.replace(/^http/, "panora")));
474
-
475
- this.layerRanges.panoramax[id] = style;
476
-
477
- // Add layers styles
478
- this.layerRanges.panoramax[id].layers = getPanoramaxLayers(style);
479
-
480
- /**
481
- * Event for Panoramax endpoint added
482
- * @event Panoramax.utils.MapStyleComposer#panoramax-added
483
- * @type {CustomEvent}
484
- * @property {string} [detail.endpoint] The added Panoramax endpoint ID
485
- */
486
- this.dispatchEvent(new CustomEvent("panoramax-added", { detail: { endpoint: id }}));
487
-
488
- if(switchOn) { this.switchPanoramaxEndpoint(id); }
489
- }
490
-
491
- /**
492
- * Uses this Panoramax Vector Tile endpoint instead of current one.
493
- * @param {string} id The endpoint ID
494
- * @memberof Panoramax.utils.MapStyleComposer#
495
- */
496
- async switchPanoramaxEndpoint(id) {
497
- if(id && !this.layerRanges.panoramax[id]) {
498
- await this.waitFor("panoramax", id);
499
- }
500
- if(!id || id === this.panoramax) { return; }
501
- this.panoramax = id;
502
-
503
- this.updateMapStyle(true);
504
-
505
- /**
506
- * Event for Panoramax endpoint changes
507
- * @event Panoramax.utils.MapStyleComposer#panoramax-changed
508
- * @type {CustomEvent}
509
- * @property {string} [detail.endpoint] The new selected Panoramax endpoint
510
- */
511
- this.dispatchEvent(new CustomEvent("panoramax-changed", { detail: { endpoint: id } }));
512
- }
513
-
514
- /**
515
- * Is the currently selected Panoramax endpoint showing data of a single user ?
516
- * @returns {boolean} True if single user
517
- * @memberof Panoramax.utils.MapStyleComposer#
518
- */
519
- isPanoramaxEndpointSingleUser() {
520
- return isPanoramaxEndpointSingleUser(this.panoramax);
521
- }
522
-
523
- /**
524
- * Changes the selected Panoramax map theme
525
- * @param {string} theme One of "default", "age", "type", "score", "gps"
526
- * @memberof Panoramax.utils.MapStyleComposer#
527
- */
528
- setPanoramaxTheme(theme) {
529
- if(theme === this.panoramaxTheme) { return; }
530
- this.panoramaxTheme = theme;
531
- this.updateMapStyle();
532
-
533
- /**
534
- * Event for Panoramax theme changes
535
- * @event Panoramax.utils.MapStyleComposer#theme-changed
536
- * @type {CustomEvent}
537
- * @property {string} [detail.theme] The new selected theme
538
- */
539
- this.dispatchEvent(new CustomEvent("theme-changed", { detail: { theme } }));
540
- }
541
-
542
- /**
543
- * Changes the selected Panoramax filters
544
- * @param {object} filters Filtering values
545
- * @param {string} [filters.minDate] Start date for pictures (format YYYY-MM-DD)
546
- * @param {string} [filters.maxDate] End date for pictures (format YYYY-MM-DD)
547
- * @param {string} [filters.pic_type] Type of picture to keep (flat, equirectangular)
548
- * @param {number[]} [filters.qualityscore] QualityScore values, as a list of 1 to 5 grades
549
- * @param {number[]} [filters.gps] GPS precision grades, as a list of 1 to 5 grades
550
- * @param {object} [filters.featuresRestrictions] Rules to restrict display of pictures & sequences
551
- * @memberof Panoramax.utils.MapStyleComposer#
552
- */
553
- setPanoramaxFilters(filters) {
554
- this.panoramaxFilters = filters;
555
- this.updateMapStyle();
556
-
557
- /**
558
- * Event for Panoramax filters changes
559
- * @event Panoramax.utils.MapStyleComposer#filters-changed
560
- * @type {CustomEvent}
561
- * @property {string} [detail.minDate] The minimum date in time range (ISO format)
562
- * @property {string} [detail.maxDate] The maximum date in time range (ISO format)
563
- * @property {string} [detail.pic_type] Camera type (equirectangular, flat, null/empty string for both)
564
- * @property {number[]} [detail.qualityscore] QualityScore values, as a list of 1 to 5 grades
565
- * @property {number[]} [detail.gps] GPS precision grades, as a list of 1 to 5 grades
566
- * @property {object} [detail.featuresRestrictions] Rules to restrict display of pictures & sequences
567
- */
568
- this.dispatchEvent(new CustomEvent("filters-changed", { detail: filters }));
569
- }
570
-
571
- /**
572
- * Creates a new data overlay
573
- * @param {string} id Identifier for this basemap
574
- * @param {object} style The MapLibre-like style definition (object with sources and layers)
575
- * @param {boolean} [switchOn=false] Immediate map display ?
576
- * @memberof Panoramax.utils.MapStyleComposer#
577
- */
578
- addDataOverlay(id, style, switchOn = false) {
579
- this.layerRanges.dataOverlays[id] = style;
580
-
581
- /**
582
- * Event for data overlay added
583
- * @event Panoramax.utils.MapStyleComposer#dataoverlay-added
584
- * @type {CustomEvent}
585
- * @property {string} [detail.dataOverlay] The added data overlay ID
586
- */
587
- this.dispatchEvent(new CustomEvent("dataoverlay-added", { detail: { dataOverlay: id }}));
588
-
589
- if(switchOn) { this.switchDataOverlayVisibility(id, true); }
590
- }
591
-
592
- /**
593
- * Change visibility of a given data Overlay.
594
- * @param {string} id The Identifier of data Overlay to change
595
- * @param {boolean} visible Set to true to make visible, false to hide
596
- * @memberof Panoramax.utils.MapStyleComposer#
597
- */
598
- async switchDataOverlayVisibility(id, visible) {
599
- if(id && !this.layerRanges.dataOverlays[id]) {
600
- await this.waitFor("dataOverlays", id);
601
- }
602
-
603
- if(visible) { this.dataOverlays.add(id); }
604
- else { this.dataOverlays.delete(id); }
605
- this.updateMapStyle();
606
-
607
- /**
608
- * Event for data overlays changes
609
- * @event Panoramax.utils.MapStyleComposer#dataoverlay-changed
610
- * @type {CustomEvent}
611
- * @property {string} [detail.dataOverlay] The data overlay ID
612
- * @property {boolean} [detail.visible] True if visible
613
- */
614
- this.dispatchEvent(new CustomEvent("dataoverlay-changed", { detail: { dataOverlay: id , visible } }));
615
- }
616
-
617
- /** @private */
618
- async _createPanoramaxEndpointFromAPI(id, api, switchOn = true) {
619
- let style;
620
-
621
- if(api._endpoints.style) { // API Style JSON
622
- style = api._endpoints.style;
623
- }
624
- else if(api._endpoints.tiles) { // API tiles
625
- style = { "sources": { "geovisio": { "tiles": [ api._endpoints.tiles ] } } };
626
- }
627
-
628
- // Load from URL
629
- if(style && typeof style === "string") {
630
- style = await fetch(style, api._getFetchOptions()).then(res => res.json());
631
- }
632
-
633
- if(style && style.sources) {
634
- this.addPanoramaxEndpoint(id, style, switchOn);
635
- }
636
- }
637
-
638
- /** @private */
639
- async _createPanoramaxEndpointForUser(userId) {
640
- // Skip if user ID is invalid
641
- if(!isPanoramaxEndpointSingleUser(userId)) { return; }
642
-
643
- // Skip if it already exist
644
- if(this.layerRanges.panoramax[userId]) {
645
- this.switchPanoramaxEndpoint(userId);
646
- return;
647
- }
648
-
649
- let style, api, apiUserId;
650
-
651
- // User from metacatalog
652
- if(userId.startsWith("metacatalog_")) {
653
- api = this._parent.apiMC;
654
- apiUserId = userId.replace(/^metacatalog_/, "");
655
- }
656
- // User from local API
657
- else {
658
- api = this._parent.api;
659
- apiUserId = userId;
660
- }
661
-
662
- // Find style from API
663
- if(api._endpoints.user_style) { // API user style JSON
664
- style = api._endpoints.user_style.replace(/\{userId\}/g, apiUserId);
665
- }
666
- else if(api._endpoints.user_tiles) { // API user tiles
667
- style = { "sources": { [`geovisio_${apiUserId}`]: {
668
- "tiles": [ api._endpoints.user_tiles.replace(/\{userId\}/g, apiUserId) ]
669
- } } };
670
- }
671
-
672
- // Load from URL
673
- if(style && typeof style === "string") {
674
- style = await fetch(style, api._getFetchOptions()).then(res => res.json());
675
- }
676
-
677
- if(style && style.sources) {
678
- this.addPanoramaxEndpoint(userId, style, true);
679
- }
680
- }
681
-
682
- /** @private */
683
- _createManyBasemaps(basemaps, switchId) {
684
- const maxzoomRgx = /\{z:(\d+)\}/;
685
- Object
686
- .entries(basemaps)
687
- .forEach(([id, def]) => {
688
- // Transform shortcuts for TMS into classic definition
689
- if(typeof def === "string" && def.includes(".png")) {
690
- let maxzoom, attribution;
691
-
692
- // Look for maxzoom if embed
693
- const maxzoomMatcher = def.match(maxzoomRgx);
694
- if(maxzoomMatcher && maxzoomMatcher.length > 1) {
695
- maxzoom = parseInt(maxzoomMatcher[1]);
696
- def = def.replace(maxzoomRgx, "{z}");
697
- }
698
-
699
- // Auto append OSM attribution
700
- if(def.includes("osm") || def.includes("openstreetmap")) {
701
- attribution = "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a>";
702
- }
703
-
704
- def = {
705
- sources: {
706
- [id]: {
707
- type: "raster",
708
- tiles: [def],
709
- tileSize: 256,
710
- maxzoom,
711
- attribution,
712
- }
713
- },
714
- layers: [{
715
- "id": id,
716
- "type": "raster",
717
- "source": id,
718
- }]
719
- };
720
- }
721
-
722
- this.addBasemap(id, def, id === switchId);
723
- });
724
- }
725
-
726
- /** @private */
727
- _createRasterBasemap(raster) {
728
- this.addBasemap("aerial", {
729
- sources: { [RASTER_LAYER_ID]: raster },
730
- layers: [{
731
- "id": RASTER_LAYER_ID,
732
- "type": "raster",
733
- "source": RASTER_LAYER_ID,
734
- }]
735
- });
736
- return RASTER_LAYER_ID;
737
- }
738
-
739
- /** @private */
740
- _applyPanoramaxThemeOnLayers(style) {
741
- const picId = this._parent.psv ? (this._parent.psv?._myVTour?.state?.loadingNode || this._parent.psv?._myVTour?.state?.currentNode?.id) : this._parent.picture;
742
- const seqId = this._parent.psv ? (picId ? this._parent.psv?._picturesSequences[picId] : null) : this._parent.sequence;
743
-
744
- style.layers = style.layers.map(layer => {
745
- const isPictures = layer.id.endsWith("_pictures");
746
- const isSequences = layer.id.endsWith("_sequences");
747
- const isGrid = layer.id.endsWith("_grid");
748
-
749
- if(!layer.layout) { layer.layout = {}; }
750
- if(!layer.paint) { layer.paint = {}; }
751
-
752
- if(isPictures || isSequences) {
753
- // -- Sort --
754
- // Values
755
- // - 100 : on top / selected feature
756
- // - 90 : hidden feature
757
- // - 20-80 : custom ranges
758
- // - 10 : basic feature
759
- // - 0 : on bottom / feature with undefined property
760
-
761
- const layerSort = ["case", ["==", ["get", "hidden"], true], 90];
762
-
763
- // Selected sequence style
764
- if(isSequences && seqId) {
765
- layerSort.push(["==", ["get", "id"], seqId], 100);
766
- }
767
- else if(isPictures && seqId) {
768
- layerSort.push(["in", seqId, ["get", "sequences"]], 100);
769
- }
770
-
771
- layerSort.push(...MAP_THEMES_STYLES[this.panoramaxTheme].sort);
772
- layerSort.push(10);
773
- layer.layout[`${isPictures ? "circle" : "line"}-sort-key`] = layerSort;
774
-
775
-
776
- // -- Color --
777
- const layerColor = ["case", ["==", ["get", "hidden"], true], COLORS.HIDDEN];
778
-
779
- // Selected sequence style
780
- if(isSequences && seqId) {
781
- layerColor.push(["==", ["get", "id"], seqId], COLORS.SELECTED);
782
- }
783
- else if(isPictures && seqId) {
784
- layerColor.push(["in", seqId, ["get", "sequences"]], COLORS.SELECTED);
785
- }
786
-
787
- layerColor.push(...MAP_THEMES_STYLES[this.panoramaxTheme].color);
788
- layer.paint[`${isPictures ? "circle" : "line"}-color`] = layerColor;
789
- if(isPictures) { layer.paint["circle-stroke-color"] = layerColor; }
790
- }
791
-
792
- if(isGrid) {
793
- let newType = "coef";
794
- if(this.panoramaxFilters.pic_type) {
795
- newType = this.panoramaxFilters.pic_type === "flat" ? "coef_flat_pictures" : "coef_360_pictures";
796
- if(hasStyleLoggedGridStats(style) && getUserAccount()) {
797
- newType = "logged_" + newType;
798
- }
799
- }
800
- layer = switchCoefValue(layer, newType);
801
- }
802
-
803
- return layer;
804
- });
805
-
806
- return style.layers;
807
- }
808
-
809
- /** @private */
810
- _applyPanoramaxFiltersOnLayers(layers) {
811
- const pf = this.panoramaxFilters;
812
-
813
- layers.forEach(layer => {
814
- const isPictures = layer.id.endsWith("_pictures");
815
- const isSequences = layer.id.endsWith("_sequences") || layer.id.endsWith("_sequences_plus");
816
-
817
- if(!isPictures && !isSequences) { return; }
818
- let layerFilters = [];
819
-
820
- if(pf.minDate && pf.minDate !== "") {
821
- if(isPictures) { layerFilters.push([">=", ["get", "ts"], pf.minDate]); }
822
- else if(isSequences) { layerFilters.push([">=", ["get", "date"], pf.minDate]); }
823
- }
824
-
825
- if(pf.maxDate && pf.maxDate !== "") {
826
- if(isSequences) { layerFilters.push(["<=", ["get", "date"], pf.maxDate]); }
827
-
828
- // Get tomorrow date for pictures filtering
829
- // (because ts is date+time, so comparing date only string would fail otherwise)
830
- if(isPictures) {
831
- let d = new Date(pf.maxDate);
832
- d.setDate(d.getDate() + 1);
833
- d = d.toISOString().split("T")[0];
834
- layerFilters.push(["<=", ["get", "ts"], d]);
835
- }
836
- }
837
-
838
- if(pf.pic_type && pf.pic_type !== "") {
839
- pf.pic_type = pf.pic_type === "flat" ? "flat" : "equirectangular";
840
- if(isSequences) { layerFilters.push(["==", ["get", "type"], pf.pic_type]); }
841
- else if(isPictures) { layerFilters.push(["==", ["get", "type"], pf.pic_type]); }
842
- }
843
-
844
- if(pf.qualityscore && pf.qualityscore.length > 0) {
845
- if(isSequences) { layerFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", pf.qualityscore]]); }
846
- else if (isPictures) { layerFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", pf.qualityscore]]); }
847
- }
848
-
849
- if(pf.gps && pf.gps.length > 0) {
850
- if(isSequences) { layerFilters.push(["in", MAP_EXPR_QUALITYSCORE_GPS, ["literal", pf.gps]]); }
851
- else if(isPictures) { layerFilters.push(["in", MAP_EXPR_QUALITYSCORE_GPS, ["literal", pf.gps]]); }
852
- }
853
-
854
- if(isPictures && pf.featuresRestrictions?.pictures && pf.featuresRestrictions.pictures.features?.length >= 0) {
855
- if(pf.featuresRestrictions.pictures.rule === "keep") {
856
- layerFilters.push(["in", ["get", "id"], ["literal", pf.featuresRestrictions.pictures.features]]);
857
- }
858
- else if(pf.featuresRestrictions.pictures.features.length > 0) {
859
- layerFilters.push(["!", ["in", ["get", "id"], ["literal", pf.featuresRestrictions.pictures.features]]]);
860
- }
861
- }
862
-
863
- if(isSequences && pf.featuresRestrictions?.sequences && pf.featuresRestrictions.sequences.features?.length >= 0) {
864
- if(pf.featuresRestrictions.sequences.rule === "keep") {
865
- layerFilters.push(["in", ["get", "id"], ["literal", pf.featuresRestrictions.sequences.features]]);
866
- }
867
- else if(pf.featuresRestrictions.sequences.features.length > 0) {
868
- layerFilters.push(["!", ["in", ["get", "id"], ["literal", pf.featuresRestrictions.sequences.features]]]);
869
- }
870
- }
871
-
872
-
873
- if(layerFilters.length === 0) { layerFilters = ["literal", true]; }
874
- else {
875
- layerFilters.unshift("all");
876
- if(isPictures) {
877
- layerFilters = ["step", ["zoom"],
878
- true,
879
- TILES_PICTURES_ZOOM, layerFilters
880
- ];
881
- }
882
- }
883
-
884
- if(layerFilters) { layer.filter = layerFilters; }
885
- });
886
-
887
- return layers;
888
- }
889
- }