@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,928 +0,0 @@
1
- import { isNullId } from "./utils.js";
2
-
3
- /**
4
- * API contains various utility functions to communicate with Panoramax/STAC API
5
- *
6
- * @class Panoramax.utils.API
7
- * @typicalname api
8
- * @fires Panoramax.utils.API#ready
9
- * @fires Panoramax.utils.API#broken
10
- * @param {string} endpoint The endpoint. It corresponds to the <a href="https://github.com/radiantearth/stac-api-spec/blob/main/overview.md#example-landing-page">STAC landing page</a>, with all links describing the API capabilities.
11
- * @param {object} [options] Options
12
- * @param {string} [options.tiles] API route serving pictures & sequences vector tiles
13
- * @param {boolean} [options.skipReadLanding=false] True to not call API landing page automatically
14
- * @param {object} [options.fetch] Set custom options for fetch calls made against API ([same syntax as fetch options parameter](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters))
15
- */
16
- export default class API extends EventTarget {
17
- constructor(endpoint, options = {}) {
18
- super();
19
-
20
- if(endpoint === null || endpoint === undefined || typeof endpoint !== "string") {
21
- const e = new Error("endpoint parameter is empty or not a valid string");
22
- this.dispatchEvent(new CustomEvent("broken", {detail: {error: e}}));
23
- throw e;
24
- }
25
-
26
- // Parse local endpoints
27
- if(endpoint.startsWith("/")) {
28
- endpoint = window.location.href.split("/").slice(0, 3).join("/") + endpoint;
29
- }
30
-
31
- // Check endpoint
32
- if(!API.isValidHttpUrl(endpoint)) {
33
- const e = new Error(`endpoint parameter is not a valid URL: ${endpoint}`);
34
- this.dispatchEvent(new CustomEvent("broken", {detail: {error: e}}));
35
- throw e;
36
- }
37
-
38
- this._endpoint = endpoint;
39
- this._isReady = 0;
40
- this._dataBbox = null;
41
- this._fetchOpts = options?.fetch || {};
42
- this._metadata = {};
43
- this._configuration = null;
44
-
45
- if(options.skipReadLanding) { return; }
46
- this._readLanding = fetch(endpoint, this._getFetchOptions())
47
- .then(res => res.json())
48
- .then(landing => this._parseLanding(landing, options))
49
- .catch(e => {
50
- this._isReady = -1;
51
- console.error(e);
52
-
53
- /**
54
- * Event when API is broken.
55
- * This happens on any API loading or map styling issue.
56
- * @event Panoramax.utils.API#broken
57
- * @type {CustomEvent}
58
- * @property {Error} detail.error The original error
59
- */
60
- this.dispatchEvent(new CustomEvent("broken", {detail: {error: e}}));
61
-
62
- return Promise.reject("Viewer failed to communicate with API");
63
- })
64
- .then(() => {
65
- this._isReady = 1;
66
-
67
- /**
68
- * Event when API is ready to use.
69
- * This happens after initial API read and map styles load.
70
- * @event Panoramax.utils.API#ready
71
- * @type {Event}
72
- */
73
- this.dispatchEvent(new Event("ready"));
74
-
75
- return "API is ready";
76
- });
77
- }
78
-
79
- /**
80
- * Resolves when the API is ready to be used.
81
- * @memberof Panoramax.utils.API#
82
- * @returns {Promise}
83
- * @fulfil {string} "API is ready" when initialization is complete.
84
- * @reject {string} Error message
85
- */
86
- onceReady() {
87
- if(this._isReady === -1) {
88
- return Promise.reject("Viewer failed to communicate with API");
89
- }
90
- else if(this._isReady === 1) {
91
- return Promise.resolve("API is ready");
92
- }
93
- else {
94
- return this._readLanding;
95
- }
96
- }
97
-
98
- /**
99
- * Checks if the API is ready to be used.
100
- * @memberof Panoramax.utils.API#
101
- * @returns {boolean} True if the API is ready, false otherwise.
102
- */
103
- isReady() {
104
- return this._isReady === 1;
105
- }
106
-
107
- /**
108
- * List of available features offered by API
109
- * @memberof Panoramax.utils.API#
110
- * @returns {string[]} Keywords of enabled features
111
- */
112
- getAvailableFeatures() {
113
- return Object.entries(this._endpoints).filter(e => e[1] !== null).map(e => e[0]);
114
- }
115
-
116
- /**
117
- * List of unavailable features on API
118
- * @memberof Panoramax.utils.API#
119
- * @returns {string[]} Keywords of disabled features
120
- */
121
- getUnavailableFeatures() {
122
- return Object.entries(this._endpoints).filter(e => e[1] === null).map(e => e[0]);
123
- }
124
-
125
- /**
126
- * Interprets JSON landing page and store important information
127
- * @memberof Panoramax.utils.API#
128
- * @private
129
- */
130
- _parseLanding(landing, options) {
131
- this._endpoints = {
132
- "collections": null,
133
- "search": null,
134
- "style": null,
135
- "user_style": null,
136
- "tiles": options?.tiles || null,
137
- "user_tiles": null,
138
- "user_search": null,
139
- "collection_preview": null,
140
- "item_preview": null,
141
- "rss": null,
142
- "report": null,
143
- "queryables": null,
144
- };
145
-
146
- if(!landing || !landing.links || !Array.isArray(landing.links)) {
147
- throw new Error("API Landing page doesn't contain 'links' list");
148
- }
149
-
150
- if(!landing.stac_version.startsWith("1.")) {
151
- throw new Error(`API is not in a supported STAC version (Panoramax viewer supports only 1.x, API is ${landing.stac_version})`);
152
- }
153
-
154
- // Read metadata
155
- this._metadata.name = landing.title || "Unnamed";
156
- this._metadata.stac_version = landing.stac_version;
157
- this._metadata.geovisio_version = landing.geovisio_version;
158
-
159
- // Read links
160
- const supportedLinks = [
161
- {
162
- rel: "search",
163
- type: "application/geo+json",
164
- endpointId: "search",
165
- mandatory: true,
166
- missingIssue: "No direct access to pictures metadata."
167
- },
168
- {
169
- rel: "data",
170
- type: "application/json",
171
- endpointId: "collections",
172
- mandatory: true,
173
- missingIssue: "No way for viewer to access sequences."
174
- },
175
- {
176
- rel: "data",
177
- type: "application/rss+xml",
178
- endpointId: "rss"
179
- },
180
- {
181
- rel: "xyz",
182
- type: "application/vnd.mapbox-vector-tile",
183
- endpointId: "tiles"
184
- },
185
- {
186
- rel: "xyz-style",
187
- type: "application/json",
188
- endpointId: "style"
189
- },
190
- {
191
- rel: "user-xyz-style",
192
- type: "application/json",
193
- endpointId: "user_style"
194
- },
195
- {
196
- rel: "user-xyz",
197
- type: "application/vnd.mapbox-vector-tile",
198
- endpointId: "user_tiles"
199
- },
200
- {
201
- rel: "user-search",
202
- type: "application/json",
203
- endpointId: "user_search",
204
- missingIssue: "Filter map data by user name will not be available."
205
- },
206
- {
207
- rel: "collection-preview",
208
- type: "image/jpeg",
209
- endpointId: "collection_preview",
210
- missingIssue: "Display of thumbnail could be slower."
211
- },
212
- {
213
- rel: "item-preview",
214
- type: "image/jpeg",
215
- endpointId: "item_preview",
216
- missingIssue: "Display of thumbnail could be slower."
217
- },
218
- {
219
- rel: "report",
220
- type: "application/json",
221
- endpointId: "report"
222
- },
223
- {
224
- rel: "http://www.opengis.net/def/rel/ogc/1.0/queryables",
225
- type: "application/schema+json",
226
- endpointId: "queryables",
227
- missingIssue: "Tags map overlay will not be available."
228
- }
229
- ];
230
-
231
- const blockingIssues = [];
232
- const warningIssues = [];
233
-
234
- supportedLinks.forEach(sl => {
235
- // Find link in landing
236
- const ll = landing.links.find(ll => ll.rel === sl.rel && ll.type === sl.type);
237
-
238
- // No link found
239
- if(!ll) {
240
- if(!this._endpoints[sl.endpointId]) {
241
- let label = `API doesn't offer a '${sl.rel}' (${sl.type}) endpoint in its links`;
242
- if(sl.missingIssue) { label += `\n${sl.missingIssue}`; }
243
-
244
- // Display issue (either blocking or not)
245
- if(sl.mandatory) { blockingIssues.push(label); }
246
- else if(sl.missingIssue) { warningIssues.push(label); }
247
- }
248
- }
249
- // Link found
250
- else {
251
- // Invalid link
252
- if(!API.isValidHttpUrl(ll.href)) {
253
- throw new Error(`API endpoint '${ll.rel}' (${ll.type}) is not a valid URL: ${ll.href}`);
254
- }
255
-
256
- // Valid link -> stored in endpoints
257
- if(!this._endpoints[sl.endpointId]) {
258
- this._endpoints[sl.endpointId] = ll.href;
259
- }
260
- }
261
- });
262
-
263
- // Complex checks
264
- if(!this._endpoints.style && !this._endpoints.tiles) {
265
- warningIssues.push("API doesn't offer 'xyz' or 'xyz-style' endpoints in its links.\nMap widget will not be available.");
266
- }
267
- if(!this._endpoints.user_style && !this._endpoints.user_tiles) {
268
- warningIssues.push("API doesn't offer 'user-xyz' or 'user-xyz-style' endpoints in its links.\nFilter map data by user ID will not be available.");
269
- }
270
-
271
- // Display warnings & errors
272
- warningIssues.forEach(w => console.warn(w));
273
- if(blockingIssues.length > 0) {
274
- throw new Error(blockingIssues.join("\n"));
275
- }
276
-
277
- // Look for data BBox
278
- const bbox = landing?.extent?.spatial?.bbox;
279
- this._dataBbox = (
280
- bbox &&
281
- Array.isArray(bbox) &&
282
- bbox.length > 0 &&
283
- Array.isArray(bbox[0]) && bbox[0].length === 4
284
- ) ?
285
- [[bbox[0][0], bbox[0][1]], [bbox[0][2], bbox[0][3]]]
286
- : null;
287
- }
288
-
289
- /**
290
- * Get the defaults fetch options to pass during API calls
291
- * @memberof Panoramax.utils.API#
292
- * @param {boolean} [noTimeout=false] Set to true to avoid default timeout
293
- * @private
294
- * @returns {object} The fetch options
295
- */
296
- _getFetchOptions(noTimeout=false) {
297
- const res = Object.assign({}, this._fetchOpts);
298
- if(!noTimeout) {
299
- res.signal = AbortSignal.timeout(15000);
300
- }
301
- return res;
302
- }
303
-
304
- /**
305
- * Get the RequestTransformFunction for MapLibre to handle fetch options
306
- * @memberof Panoramax.utils.API#
307
- * @private
308
- * @returns {function} The RequestTransformFunction
309
- */
310
- _getMapRequestTransform() {
311
- const fetchOpts = this._getFetchOptions();
312
- delete fetchOpts.signal;
313
- return (url) => {
314
- // As MapLibre will use this function for all its calls
315
- // We must make sure fetch options are sent only for
316
- // The STAC API calls, particularly the tiles endpoint
317
- if(url.startsWith(this._endpoint)) {
318
- if(Object.keys(fetchOpts).length > 0) {
319
- return {
320
- url,
321
- ...fetchOpts
322
- };
323
- }
324
- }
325
- // Override IGN FR High-DPI sprites
326
- else if(url.includes("data.geopf.fr") && url.includes("@2x")) {
327
- return {
328
- url: url.replace("@2x", "")
329
- };
330
- }
331
- };
332
- }
333
-
334
- /**
335
- * Get the withCredentials function for PhotoSphereViewer to handle fetch credentials parameter.
336
- * @memberof Panoramax.utils.API#
337
- * @private
338
- * @returns {function} The withCredentials function
339
- */
340
- _getPSVWithCredentials() {
341
- const fetchOpts = this._getFetchOptions();
342
- if(fetchOpts.credentials === "include") {
343
- return (url) => url.startsWith(this._endpoint);
344
- }
345
- else {
346
- return undefined;
347
- }
348
- }
349
-
350
- /**
351
- * Get absolute URL from a relative version
352
- * @param {string} url The relative URL
353
- * @returns {string} The absolute URL, using API endpoint
354
- */
355
- cleanResourceURL(url) {
356
- if(!url) { return; }
357
- const endpoint = new URL(this._endpoint);
358
- if(url.startsWith("/")) { return endpoint.protocol + "//" + endpoint.host + url; }
359
- else if(url.startsWith("./")) { return this._endpoint + url.substring(1); }
360
- else { return url; }
361
- }
362
-
363
- /**
364
- * Get sequence GeoJSON representation
365
- * @memberof Panoramax.utils.API#
366
- * @param {string} seqId The sequence ID
367
- * @param {string} [next] The next link URL (only for internals)
368
- * @param {object} [data] The previous dataset (only for internals)
369
- * @param {number} [limit] How many items to retrieve (defaults to unset). If set, pages are not looked through.
370
- * @returns {Promise}
371
- * @fulfil {object} Sequence GeoJSON
372
- * @reject {Error} If API is not ready or for any network issue
373
- */
374
- async getSequenceItems(seqId, next = null, data = null, limit = null) {
375
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
376
- try {
377
- API.isIdValid(seqId);
378
- return await fetch(
379
- next || `${this._endpoints.collections}/${seqId}/items${limit ? "?limit="+limit : ""}`,
380
- this._getFetchOptions(true)
381
- )
382
- .then(res => res.json())
383
- .then(res => {
384
- // Merge previous data with current page
385
- let nextData = res;
386
- if(data) { nextData.features = data.features.concat(nextData.features); }
387
-
388
- // Handle pagination for next link
389
- const nextLink = res.links.find(l => l.rel === "next");
390
- if(!limit && nextLink) { return this.getSequenceItems(seqId, nextLink.href, nextData); }
391
- else { return nextData; }
392
- });
393
- }
394
- catch(e) {
395
- return await Promise.reject(e);
396
- }
397
- }
398
-
399
- /**
400
- * Get full URL for listing pictures around a specific location
401
- * @memberof Panoramax.utils.API#
402
- * @param {number} lat Latitude
403
- * @param {number} lon Longitude
404
- * @param {number} [factor] The radius to search around (in degrees)
405
- * @param {number} [limit] Max amount of pictures to retrieve
406
- * @param {string} [seqId] The sequence ID to filter on (by default, no filter)
407
- * @returns {string} The corresponding URL
408
- */
409
- getPicturesAroundCoordinatesUrl(lat, lon, factor = 0.0005, limit = null, seqId = null) {
410
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
411
-
412
- if(isNaN(parseFloat(lat)) || isNaN(parseFloat(lon))) {
413
- throw new Error("lat and lon parameters should be valid numbers");
414
- }
415
-
416
- const bbox = [ lon - factor, lat - factor, lon + factor, lat + factor ].map(d => d.toFixed(7)).join(",");
417
- const lim = limit ? `&limit=${limit}` : "";
418
- const seq = seqId ? `&collections=${seqId}`: "";
419
- return `${this._endpoints.search}?bbox=${bbox}${lim}${seq}`;
420
- }
421
-
422
- /**
423
- * Get list of pictures around a specific location
424
- * @memberof Panoramax.utils.API#
425
- * @param {number} lat Latitude
426
- * @param {number} lon Longitude
427
- * @param {number} [factor] The radius to search around (in degrees)
428
- * @param {number} [limit] Max amount of pictures to retrieve
429
- * @param {string} [seqId] The sequence ID to filter on (by default, no filter)
430
- * @returns {Promise}
431
- * @fulfil {object} The GeoJSON feature collection
432
- */
433
- getPicturesAroundCoordinates(lat, lon, factor, limit, seqId) {
434
- return fetch(this.getPicturesAroundCoordinatesUrl(lat, lon, factor, limit, seqId), this._getFetchOptions())
435
- .then(res => res.json());
436
- }
437
-
438
- /**
439
- * Get full URL for retrieving a specific picture metadata
440
- * @memberof Panoramax.utils.API#
441
- * @param {string} picId The picture unique identifier
442
- * @param {string} [seqId] The sequence ID
443
- * @returns {string} The corresponding URL
444
- * @throws {Error} If API is not ready
445
- */
446
- getPictureMetadataUrl(picId, seqId) {
447
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
448
-
449
- if(API.isIdValid(picId)) {
450
- if(seqId) { return `${this._endpoints.collections}/${seqId}/items/${picId}`; }
451
- else { return `${this._endpoints.search}?ids=${picId}`; }
452
- }
453
- }
454
-
455
- /**
456
- * Find the thumbnail URL for a given picture
457
- * @memberof Panoramax.utils.API#
458
- * @param {object} picture The picture GeoJSON feature
459
- * @returns {string} The thumbnail URL, or null if not found
460
- * @throws {Error} If API is not ready
461
- * @private
462
- */
463
- findThumbnailInPictureFeature(picture) {
464
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
465
- if(!picture || !picture.assets) { return null; }
466
-
467
- let visualFallback = null;
468
- for(let a of Object.values(picture.assets)) {
469
- if(a.roles.includes("thumbnail") && a.type === "image/jpeg" && API.isValidHttpUrl(a.href)) {
470
- return this.cleanResourceURL(a.href);
471
- }
472
- else if(a.roles.includes("visual") && a.type === "image/jpeg" && API.isValidHttpUrl(a.href)) {
473
- visualFallback = this.cleanResourceURL(a.href);
474
- }
475
- }
476
- return visualFallback;
477
- }
478
-
479
- /**
480
- * Get a picture thumbnail URL for a given sequence
481
- * @memberof Panoramax.utils.API#
482
- * @param {string} seqId The sequence ID
483
- * @param {object} [seq] The sequence metadata (with links) if already loaded
484
- * @returns {Promise}
485
- * @fulfil {string|null} Promise resolving on the picture thumbnail URL, or null if not found
486
- * @throws {Error} If API is not ready
487
- */
488
- getPictureThumbnailURLForSequence(seqId, seq) {
489
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
490
-
491
- // Look for a dedicated endpoint in API
492
- if(this._endpoints.collection_preview) {
493
- return Promise.resolve(this._endpoints.collection_preview.replace("{id}", seqId));
494
- }
495
-
496
- // Check if a preview link exists in sequence metadata
497
- if(seq && Array.isArray(seq.links) && seq.links.length > 0) {
498
- let preview = seq.links.find(l => l.rel === "preview" && l.type === "image/jpeg");
499
- if(preview && API.isValidHttpUrl(preview.href)) {
500
- return Promise.resolve(preview.href);
501
- }
502
- }
503
-
504
- // Otherwise, search for a single picture in collection
505
- const url = `${this._endpoints.search}?limit=1&collections=${seqId}`;
506
-
507
- return fetch(url, this._getFetchOptions())
508
- .then(res => res.json())
509
- .then(res => {
510
- if(!Array.isArray(res.features) || res.features.length === 0) {
511
- return null;
512
- }
513
-
514
- return this.findThumbnailInPictureFeature(res.features.pop());
515
- });
516
- }
517
-
518
- /**
519
- * Get thumbnail URL for a specific picture
520
- * @memberof Panoramax.utils.API#
521
- * @param {string} picId The picture unique identifier
522
- * @param {string} [seqId] The sequence ID
523
- * @returns {Promise}
524
- * @fulfil {string|null} The corresponding URL on resolve, or null if no thumbnail could be found
525
- * @throws {Error} If API is not ready
526
- */
527
- getPictureThumbnailURL(picId, seqId) {
528
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
529
-
530
- if(!picId) { return Promise.resolve(null); }
531
-
532
- // Look for a dedicated endpoint in API
533
- if(this._endpoints.item_preview) {
534
- return Promise.resolve(this._endpoints.item_preview.replace("{id}", picId));
535
- }
536
-
537
- // Pic + sequence IDs defined -> use direct item metadata
538
- if(picId && seqId) {
539
- return fetch(`${this._endpoints.collections}/${seqId}/items/${picId}`, this._getFetchOptions())
540
- .then(res => res.json())
541
- .then(picture => {
542
- return picture ? this.findThumbnailInPictureFeature(picture) : null;
543
- });
544
- }
545
-
546
- // Picture ID only -> use search as fallback
547
- return fetch(`${this._endpoints.search}?ids=${picId}`, this._getFetchOptions())
548
- .then(res => res.json())
549
- .then(res => {
550
- if(!res || !Array.isArray(res.features) || res.features.length === 0) { return null; }
551
- return this.findThumbnailInPictureFeature(res.features.pop());
552
- });
553
- }
554
-
555
- /**
556
- * Get the RSS feed URL with map parameters (if map is enabled)
557
- * @memberof Panoramax.utils.API#
558
- * @param {LngLatBounds} [bbox] The map current bounding box, or null if not available
559
- * @returns {string|null} The URL, or null if no RSS feed is available
560
- * @throws {Error} If API is not ready
561
- */
562
- getRSSURL(bbox) {
563
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
564
-
565
- if(this._endpoints.rss) {
566
- let url = this._endpoints.rss;
567
- if(bbox) {
568
- url += url.includes("?") ? "&": "?";
569
- url += "bbox=" + [bbox.getWest(), bbox.getSouth(), bbox.getEast(), bbox.getNorth()].join(",");
570
- }
571
- return url;
572
- }
573
- else {
574
- return null;
575
- }
576
- }
577
-
578
- /**
579
- * Get full URL for retrieving a specific sequence metadata
580
- * @memberof Panoramax.utils.API#
581
- * @param {string} seqId The sequence ID
582
- * @returns {string} The corresponding URL
583
- * @throws {Error} If API is not ready
584
- */
585
- getSequenceMetadataUrl(seqId) {
586
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
587
- return `${this._endpoints.collections}/${seqId}`;
588
- }
589
-
590
- /**
591
- * Retrieve metadata for a specific sequence
592
- * @memberof Panoramax.utils.API#
593
- * @param {string} seqId The sequence ID
594
- * @returns {Promise}
595
- * @fulfil {object|null} Sequence metadata
596
- * @throws {Error} If API is not ready
597
- */
598
- getSequenceMetadata(seqId) {
599
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
600
-
601
- return fetch(this.getSequenceMetadataUrl(seqId), this._getFetchOptions())
602
- .then(res => res.json());
603
- }
604
-
605
- /**
606
- * Get available data bounding box
607
- * @memberof Panoramax.utils.API#
608
- * @returns {LngLatBoundsLike} The bounding box or null if not available
609
- * @throws {Error} If API is not ready
610
- */
611
- getDataBbox() {
612
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
613
-
614
- return this._dataBbox;
615
- }
616
-
617
- /**
618
- * Look for user ID based on user name query
619
- * @memberof Panoramax.utils.API#
620
- * @param {string} query The user name to look for
621
- * @returns {Promise}
622
- * @fulfil {object|null} List of potential users
623
- * @throws {Error} If API is not ready or user search not available
624
- */
625
- searchUsers(query) {
626
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
627
- if(!this._endpoints.user_search) { throw new Error("User search is not available"); }
628
-
629
- return fetch(`${this._endpoints.user_search}?q=${query}`, this._getFetchOptions())
630
- .then(res => res.json())
631
- .then(res => {
632
- return res?.features || null;
633
- });
634
- }
635
-
636
- /**
637
- * Get user name based on its ID
638
- * @memberof Panoramax.utils.API#
639
- * @param {string} userId The user UUID
640
- * @returns {Promise}
641
- * @throws {Error} If API is not ready
642
- * @fulfil {string|null} The user name (or null if not found)
643
- */
644
- getUserName(userId) {
645
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
646
- if(!this._endpoints.user_search) { throw new Error("User search is not available"); }
647
-
648
- return fetch(this._endpoints.user_search.replace(/\/search$/, `/${userId}`), this._getFetchOptions())
649
- .then(res => res.json())
650
- .then(res => {
651
- return res?.label || res?.name || null;
652
- });
653
- }
654
-
655
- /**
656
- * Send a report to API
657
- * @memberof Panoramax.utils.API#
658
- * @param {object} data The input form data
659
- * @returns {Promise}
660
- * @fulfil {object} The JSON API response
661
- */
662
- sendReport(data) {
663
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
664
- if(!this._endpoints.report) { throw new Error("Report sending is not available"); }
665
-
666
- const opts = {
667
- ...this._getFetchOptions(),
668
- method: "POST",
669
- body: JSON.stringify(data),
670
- headers: { "Content-Type": "application/json" },
671
- };
672
- return fetch(this._endpoints.report, opts)
673
- .then(async res => {
674
- if(res.status >= 400) {
675
- let txt = await res.text();
676
- try {
677
- txt = JSON.parse(txt)["message"];
678
- }
679
- catch(e) {}
680
- return Promise.reject(txt);
681
- }
682
- return res.json();
683
- });
684
- }
685
-
686
- /**
687
- * Send picture semantics change to origin API.
688
- * @memberof Panoramax.utils.API#
689
- * @param {object} picMeta The picture metadata
690
- * @param {object} semanticsDiff The difference in semantics compared to original data
691
- * @returns {Promise}
692
- * @fulfil {object} The JSON API response
693
- */
694
- sendPictureSemantics(picMeta, semanticsDiff) {
695
- // Fake send version
696
- // Const newMeta = Object.assign({}, picMeta);
697
- // NewMeta.properties.semantics = newMeta.properties.semantics.concat(semanticsDiff).filter(t => (
698
- // T.action != "delete"
699
- // && !semanticsDiff.find(t2 => t.key == t2.key && t.value == t2.value && t2.action == "delete")
700
- // ));
701
- // Return Promise.resolve(newMeta);
702
-
703
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
704
- if(!picMeta?.sequence?.id || !picMeta?.id) { throw new Error("Missing IDs from picture"); }
705
-
706
- // Check if it's from metacatalog
707
- let picLink = this.getPictureMetadataUrl(picMeta.id, picMeta.sequence.id);
708
- if(picMeta?.origInstance) {
709
- picLink = `${picMeta.origInstance.href}/api/collections/${picMeta.sequence.id}/items/${picMeta.id}`;
710
- }
711
-
712
- const opts = {
713
- ...this._getFetchOptions(),
714
- method: "PATCH",
715
- body: JSON.stringify({ semantics: semanticsDiff }),
716
- headers: { "Content-Type": "application/json" },
717
- };
718
-
719
- return fetch(picLink, opts)
720
- .then(async res => {
721
- if(res.status >= 400) {
722
- let txt = await res.text();
723
- try {
724
- txt = JSON.parse(txt)["message"];
725
- }
726
- catch(e) {}
727
- return Promise.reject(txt);
728
- }
729
- return res.json();
730
- });
731
- }
732
-
733
- /**
734
- * Create new annotation for a given picture on its origin API.
735
- * @memberof Panoramax.utils.API#
736
- * @param {object} picMeta The picture metadata
737
- * @param {object} shape The annotation shape
738
- * @param {object} semanticsDiff The difference in semantics compared to original data
739
- * @returns {Promise}
740
- * @fulfil {object} The JSON API response
741
- */
742
- createPictureAnnotation(picMeta, shape, semanticsDiff) {
743
- // Fake send version
744
- // Return Promise.resolve({
745
- // "id": "fe298d59-a9c6-4680-8e76-6efe82d97f48",
746
- // "semantics": semanticsDiff,
747
- // "shape": { "type": "Polygon", "coordinates": [[
748
- // [shape[0], shape[1]],
749
- // [shape[0], shape[3]],
750
- // [shape[2], shape[3]],
751
- // [shape[2], shape[1]],
752
- // [shape[0], shape[1]],
753
- // ]] }
754
- // });
755
-
756
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
757
- if(!picMeta?.sequence?.id || !picMeta?.id) { throw new Error("Missing IDs from picture"); }
758
-
759
- // Check if it's from metacatalog
760
- let picLink = this.getPictureMetadataUrl(picMeta.id, picMeta.sequence.id)+"/annotations";
761
- if(picMeta?.origInstance) {
762
- picLink = `${picMeta.origInstance.href}/api/collections/${picMeta.sequence.id}/items/${picMeta.id}/annotations`;
763
- }
764
-
765
- const opts = {
766
- ...this._getFetchOptions(),
767
- method: "POST",
768
- body: JSON.stringify({
769
- semantics: semanticsDiff,
770
- shape: shape
771
- }),
772
- headers: { "Content-Type": "application/json" },
773
- };
774
-
775
- return fetch(picLink, opts)
776
- .then(async res => {
777
- if(res.status >= 400) {
778
- let txt = await res.text();
779
- try {
780
- txt = JSON.parse(txt)["message"];
781
- }
782
- catch(e) {}
783
- return Promise.reject(txt);
784
- }
785
- return res.json();
786
- });
787
- }
788
-
789
- /**
790
- * Edit semantics of an existing annotation on its origin API.
791
- * @memberof Panoramax.utils.API#
792
- * @param {string} annotationId The annotation UUID
793
- * @param {object} semanticsDiff The difference in semantics compared to original data
794
- * @returns {Promise}
795
- * @fulfil {object} The JSON API response
796
- */
797
- editPictureAnnotation(annotationId, semanticsDiff) {
798
- // Fake send version
799
- // Return Promise.resolve({
800
- // "id": annotationId,
801
- // "semantics": semanticsDiff,
802
- // "shape": {
803
- // "coordinates": [[[2296,946],[2336,946],[2336,978],[2296,978],[2296,946]]],
804
- // "type": "Polygon"
805
- // }
806
- // });
807
-
808
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
809
- let annotLink = `${this._endpoint}/annotations/${annotationId}`;
810
-
811
- const opts = {
812
- ...this._getFetchOptions(),
813
- method: "PATCH",
814
- body: JSON.stringify({ semantics: semanticsDiff }),
815
- headers: { "Content-Type": "application/json" },
816
- };
817
-
818
- return fetch(annotLink, opts)
819
- .then(async res => {
820
- if(res.status >= 400) {
821
- let txt = await res.text();
822
- try {
823
- txt = JSON.parse(txt)["message"];
824
- }
825
- catch(e) {}
826
- return Promise.reject(txt);
827
- }
828
- return res.json();
829
- });
830
- }
831
-
832
- /**
833
- * Delete a single annotation on its origin API.
834
- * @memberof Panoramax.utils.API#
835
- * @param {string} annotationId The annotation UUID
836
- * @returns {Promise}
837
- * @fulfil {object} The JSON API response
838
- */
839
- deletePictureAnnotation(annotationId) {
840
- // Fake send version
841
- // Console.log("Delete", annotationId);
842
- // Return Promise.resolve(true);
843
-
844
- if(!this.isReady()) { throw new Error("API is not ready to use"); }
845
- let annotLink = `${this._endpoint}/annotations/${annotationId}`;
846
-
847
- const opts = {
848
- ...this._getFetchOptions(),
849
- method: "DELETE",
850
- };
851
-
852
- return fetch(annotLink, opts)
853
- .then(async res => {
854
- if(res.status >= 400) {
855
- let txt = await res.text();
856
- try {
857
- txt = JSON.parse(txt)["message"];
858
- }
859
- catch(e) {}
860
- return Promise.reject(txt);
861
- }
862
- return true;
863
- });
864
- }
865
-
866
- /**
867
- * Get the URL for user to log-in.
868
- * @memberof Panoramax.utils.API#
869
- * @returns {Promise}
870
- * @fulfil {string|boolean} The log-in URL, or false if no login is available
871
- */
872
- getAuthURL() {
873
- if(this._configuration) {
874
- return Promise.resolve(
875
- this._configuration?.auth?.enabled
876
- ? `${this._endpoint}/auth/login?next_url=<CBURL>`
877
- : false
878
- );
879
- }
880
- else if(this._configuration === null) {
881
- return fetch(this._endpoint+"/configuration", this._getFetchOptions())
882
- .then(res => res.json())
883
- .then(res => {
884
- this._configuration = res;
885
- return this.getAuthURL();
886
- })
887
- .catch(() => {
888
- this._configuration = false;
889
- return false;
890
- });
891
- }
892
- else {
893
- return Promise.resolve(false);
894
- }
895
- }
896
-
897
- /**
898
- * Checks URL string validity
899
- * @memberof Panoramax.utils.API
900
- * @param {string} str The URL to check
901
- * @returns {boolean} True if valid
902
- */
903
- static isValidHttpUrl(str) {
904
- let url;
905
-
906
- try {
907
- url = new URL(str);
908
- } catch (_) {
909
- return false;
910
- }
911
-
912
- return url.protocol === "http:" || url.protocol === "https:";
913
- }
914
-
915
- /**
916
- * Checks picture or sequence ID validity
917
- * @memberof Panoramax.utils.API
918
- * @param {string} id The ID to check
919
- * @returns {boolean} True if valid
920
- * @throws {Error} If not valid
921
- */
922
- static isIdValid(id) {
923
- if(isNullId(id)) {
924
- throw new Error("id should be a valid picture unique identifier");
925
- }
926
- return true;
927
- }
928
- }