@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.
- package/build/cjs/index.js +1 -1
- package/build/cjs/index_photoviewer.js +1 -1
- package/build/esm/components/core/Basic.js +1 -1
- package/build/esm/translations/el.json +92 -1
- package/package.json +1 -1
- package/build/bundle.cjs +0 -3399
- package/build/bundle.cjs.map +0 -1
- package/build/bundle_photoviewer.cjs +0 -2510
- package/build/bundle_photoviewer.cjs.map +0 -1
- package/build/components/core/Basic.css +0 -56
- package/build/components/core/Basic.js +0 -378
- package/build/components/core/CoverageMap.css +0 -10
- package/build/components/core/CoverageMap.js +0 -169
- package/build/components/core/Editor.css +0 -33
- package/build/components/core/Editor.js +0 -398
- package/build/components/core/PhotoViewer.css +0 -70
- package/build/components/core/PhotoViewer.js +0 -650
- package/build/components/core/Viewer.css +0 -130
- package/build/components/core/Viewer.js +0 -711
- package/build/components/core/index.js +0 -10
- package/build/components/index.js +0 -11
- package/build/components/index_photoviewer.js +0 -6
- package/build/components/layout/BottomDrawer.js +0 -258
- package/build/components/layout/CorneredGrid.js +0 -143
- package/build/components/layout/Mini.js +0 -121
- package/build/components/layout/Tabs.js +0 -140
- package/build/components/layout/index.js +0 -9
- package/build/components/menus/LocationPrecisionDoc.js +0 -42
- package/build/components/menus/MapBackground.js +0 -110
- package/build/components/menus/MapFilters.js +0 -567
- package/build/components/menus/MapLayers.js +0 -238
- package/build/components/menus/MapLegend.js +0 -68
- package/build/components/menus/MiniPictureLegend.js +0 -73
- package/build/components/menus/PictureLegend.js +0 -379
- package/build/components/menus/PictureMetadata.js +0 -380
- package/build/components/menus/PlayerOptions.js +0 -93
- package/build/components/menus/QualityScoreDoc.js +0 -42
- package/build/components/menus/ReportForm.js +0 -132
- package/build/components/menus/SemanticsDoc.js +0 -38
- package/build/components/menus/SemanticsDownload.js +0 -33
- package/build/components/menus/SemanticsFilters.js +0 -153
- package/build/components/menus/SemanticsList.js +0 -413
- package/build/components/menus/SemanticsMetadata.js +0 -368
- package/build/components/menus/Share.js +0 -105
- package/build/components/menus/index.js +0 -22
- package/build/components/menus/index_photoviewer.js +0 -11
- package/build/components/styles.js +0 -557
- package/build/components/ui/AnnotationsSwitch.js +0 -159
- package/build/components/ui/Button.js +0 -77
- package/build/components/ui/ButtonGroup.css +0 -59
- package/build/components/ui/ButtonGroup.js +0 -69
- package/build/components/ui/CopyButton.js +0 -110
- package/build/components/ui/Grade.js +0 -54
- package/build/components/ui/GradeFilter.js +0 -122
- package/build/components/ui/IconSwitch.js +0 -193
- package/build/components/ui/LinkButton.js +0 -67
- package/build/components/ui/ListGroup.js +0 -66
- package/build/components/ui/ListItem.js +0 -90
- package/build/components/ui/Loader.js +0 -203
- package/build/components/ui/Map.css +0 -63
- package/build/components/ui/Map.js +0 -853
- package/build/components/ui/MapMore.js +0 -175
- package/build/components/ui/Photo.css +0 -50
- package/build/components/ui/Photo.js +0 -1502
- package/build/components/ui/Popup.js +0 -145
- package/build/components/ui/ProgressBar.js +0 -104
- package/build/components/ui/QualityScore.js +0 -147
- package/build/components/ui/SearchBar.js +0 -374
- package/build/components/ui/SemanticsEditor.js +0 -191
- package/build/components/ui/SemanticsTable.js +0 -88
- package/build/components/ui/Switch.js +0 -139
- package/build/components/ui/TogglableGroup.js +0 -157
- package/build/components/ui/index.js +0 -29
- package/build/components/ui/index_photoviewer.js +0 -21
- package/build/components/ui/widgets/CopyCoordinates.js +0 -75
- package/build/components/ui/widgets/GeoSearch.css +0 -21
- package/build/components/ui/widgets/GeoSearch.js +0 -150
- package/build/components/ui/widgets/Legend.js +0 -190
- package/build/components/ui/widgets/LevelSelect.css +0 -51
- package/build/components/ui/widgets/LevelSelect.js +0 -143
- package/build/components/ui/widgets/MapFiltersButton.js +0 -114
- package/build/components/ui/widgets/MapLayersButton.js +0 -79
- package/build/components/ui/widgets/OSMEditors.js +0 -155
- package/build/components/ui/widgets/PictureLegendActions.js +0 -99
- package/build/components/ui/widgets/Player.css +0 -7
- package/build/components/ui/widgets/Player.js +0 -154
- package/build/components/ui/widgets/SemanticsFiltersButton.js +0 -65
- package/build/components/ui/widgets/Zoom.js +0 -84
- package/build/components/ui/widgets/index.js +0 -16
- package/build/components/ui/widgets/index_photoviewer.js +0 -7
- package/build/img/arrow_360.svg +0 -14
- package/build/img/arrow_flat.svg +0 -11
- package/build/img/arrow_triangle.svg +0 -9
- package/build/img/arrow_turn.svg +0 -8
- package/build/img/bg_aerial.jpg +0 -0
- package/build/img/bg_streets.jpg +0 -0
- package/build/img/loader_base.jpg +0 -0
- package/build/img/logo_dead.svg +0 -91
- package/build/img/marker.svg +0 -17
- package/build/img/marker_blue.svg +0 -20
- package/build/img/osm.svg +0 -49
- package/build/img/panoramax.svg +0 -13
- package/build/img/switch_big.svg +0 -54
- package/build/img/switch_mini.svg +0 -48
- package/build/img/wd.svg +0 -1
- package/build/index_photoviewer.js +0 -4
- package/build/package.json +0 -148
- package/build/servers.js +0 -14
- package/build/translations/ar.json +0 -1
- package/build/translations/be.json +0 -257
- package/build/translations/br.json +0 -81
- package/build/translations/cy.json +0 -117
- package/build/translations/da.json +0 -300
- package/build/translations/de.json +0 -309
- package/build/translations/en.json +0 -294
- package/build/translations/eo.json +0 -235
- package/build/translations/es.json +0 -292
- package/build/translations/fi.json +0 -1
- package/build/translations/fr.json +0 -294
- package/build/translations/hr.json +0 -294
- package/build/translations/hu.json +0 -294
- package/build/translations/it.json +0 -306
- package/build/translations/ja.json +0 -182
- package/build/translations/ko.json +0 -1
- package/build/translations/nl.json +0 -305
- package/build/translations/nn.json +0 -1
- package/build/translations/pl.json +0 -169
- package/build/translations/pt.json +0 -296
- package/build/translations/pt_BR.json +0 -304
- package/build/translations/sv.json +0 -182
- package/build/translations/ti.json +0 -9
- package/build/translations/tr.json +0 -297
- package/build/translations/uk.json +0 -268
- package/build/translations/zh_Hant.json +0 -309
- package/build/utils/API.js +0 -928
- package/build/utils/InitParameters.js +0 -521
- package/build/utils/MapStyleComposer.js +0 -889
- package/build/utils/PanoraMapProtocol.js +0 -49
- package/build/utils/PhotoAdapter.js +0 -49
- package/build/utils/PresetsManager.js +0 -148
- package/build/utils/SemanticsMapProtocol.js +0 -144
- package/build/utils/URLHandler.js +0 -426
- package/build/utils/geocoder.js +0 -203
- package/build/utils/i18n.js +0 -128
- package/build/utils/index.js +0 -17
- package/build/utils/index_photoviewer.js +0 -14
- package/build/utils/indoor.js +0 -200
- package/build/utils/map.js +0 -788
- package/build/utils/picture.js +0 -507
- package/build/utils/semantics.js +0 -321
- package/build/utils/services.js +0 -148
- package/build/utils/utils.js +0 -433
- package/build/utils/widgets.js +0 -110
package/build/utils/API.js
DELETED
|
@@ -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
|
-
}
|