@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,1502 +0,0 @@
1
- import {
2
- BASE_PANORAMA_ID, getDistance, getRelativeHeading,
3
- getRoadAbsoluteHeading, isNullId, positionToXYZ,
4
- xyzToPosition,
5
- } from "../../utils/utils.js";
6
- import { apiFeatureToPSVNode } from "../../utils/picture.js";
7
-
8
- // Photo Sphere Viewer imports
9
- import { CONSTANTS, Viewer as PSViewer } from "@photo-sphere-viewer/core";
10
- import { VirtualTourPlugin } from "@photo-sphere-viewer/virtual-tour-plugin";
11
- import { MarkersPlugin } from "@photo-sphere-viewer/markers-plugin";
12
- import PhotoAdapter from "../../utils/PhotoAdapter.js";
13
- import PSVCoreStyles from "@photo-sphere-viewer/core/index.css" with { type: "css" };
14
- import PSVVirtualTourStyles from "@photo-sphere-viewer/virtual-tour-plugin/index.css" with { type: "css" };
15
- import PSVMarkersStyles from "@photo-sphere-viewer/markers-plugin/index.css" with { type: "css" };
16
- import PhotoStyles from "./Photo.css" with { type: "css" };
17
- document.adoptedStyleSheets.push(PSVCoreStyles);
18
- document.adoptedStyleSheets.push(PSVVirtualTourStyles);
19
- document.adoptedStyleSheets.push(PSVMarkersStyles);
20
- document.adoptedStyleSheets.push(PhotoStyles);
21
-
22
- const LogoDead = await fetch(new URL("../../img/logo_dead.svg", import.meta.url).href).then(res => res.text());
23
- const LoaderImgBase = new URL("../../img/loader_base.jpg", import.meta.url).href;
24
-
25
-
26
- // Default panorama (logo)
27
- const BASE_PANORAMA = {
28
- baseUrl: LoaderImgBase,
29
- width: 1280,
30
- cols: 2,
31
- rows: 1,
32
- tileUrl: () => null,
33
- };
34
- const BASE_PANORAMA_NODE = {
35
- id: BASE_PANORAMA_ID,
36
- caption: "",
37
- panorama: BASE_PANORAMA,
38
- links: [],
39
- gps: [0,0],
40
- sequence: {},
41
- sphereCorrection: {},
42
- horizontalFov: 360,
43
- properties: {},
44
- };
45
-
46
- export const PSV_DEFAULT_ZOOM = 30;
47
- export const PSV_ANIM_DURATION = 250;
48
- export const PIC_MAX_STAY_DURATION = 3000;
49
-
50
- const DRAW_ANNOT_1ST = "draw-annotation-1";
51
- const DRAW_ANNOT_2ND = "draw-annotation-2";
52
- const DRAW_ANNOT_RECT = "draw-annotation-r";
53
- const DRAW_ANNOT_COLORS = {
54
- CORNER_STROKE: "rgb(224, 64, 251)",
55
- CORNER_FILL: "rgb(224, 64, 251, 0.5)",
56
- RECT_STROKE: "rgb(123, 31, 162)",
57
- RECT_FILL: "rgba(123, 31, 162, 0.5)",
58
- };
59
-
60
- PSViewer.useNewAnglesOrder = true;
61
-
62
- const keepOnlyPSVOptions = opts => Object.fromEntries(
63
- Object.entries(opts).filter(
64
- ([k,]) => !["plugins", "shouldGoFast", "displayAnnotations", "transitionDuration", "picturesNavigation"].includes(k)
65
- )
66
- );
67
-
68
-
69
- /**
70
- * Triggered once when the panorama image has been loaded and the viewer is ready to perform the first render.
71
- * @see {@link https://photo-sphere-viewer.js.org/guide/events.html#ready|Photo Sphere Viewer documentation}
72
- * @event Panoramax.components.ui.Photo#ready
73
- * @memberof Panoramax.components.ui.Photo
74
- * @type {Event}
75
- */
76
-
77
- /**
78
- * Photo is the component showing a single picture.
79
- * It uses Photo Sphere Viewer as a basis, and pre-configure dialog with STAC API.
80
- *
81
- * Note that all functions of [PhotoSphereViewer Viewer class](https://photo-sphere-viewer.js.org/api/classes/core.viewer) are available as well.
82
- *
83
- * @class Panoramax.components.ui.Photo
84
- * @extends [photo-sphere-viewer.core.Viewer](https://photo-sphere-viewer.js.org/api/classes/Core.Viewer.html)
85
- * @param {Panoramax.components.core.basic} parent The parent view
86
- * @param {Element} container The DOM element to create into
87
- * @param {object} [options] The viewer options. Can be any of [Photo Sphere Viewer options](https://photo-sphere-viewer.js.org/guide/config.html#standard-options)
88
- * @param {number} [options.transitionDuration] The number of milliseconds the transition animation should be.
89
- * @param {number[]} [options.position] Initial geographical coordinates (as [latitude, longitude]) to find picture nearby. Only used if no picture ID is set.
90
- * @param {function} [options.shouldGoFast] Function returning a boolean to indicate if we may skip loading HD images.
91
- * @param {string} [options.picturesNavigation=any] The allowed pictures navigation ("any": no restriction, "seq": only pictures in same sequence, "pic": only selected picture)
92
- * @param {boolean} [options.displayAnnotations=false] Should pictures annotations show up automatically if they are present ? Set to true to enable.
93
- * @fires Panoramax.components.ui.Photo#picture-loading
94
- * @fires Panoramax.components.ui.Photo#picture-preview-started
95
- * @fires Panoramax.components.ui.Photo#picture-preview-stopped
96
- * @fires Panoramax.components.ui.Photo#view-rotated
97
- * @fires Panoramax.components.ui.Photo#picture-loaded
98
- * @fires Panoramax.components.ui.Photo#picture-tiles-loaded
99
- * @fires Panoramax.components.ui.Photo#transition-duration-changed
100
- * @fires Panoramax.components.ui.Photo#sequence-playing
101
- * @fires Panoramax.components.ui.Photo#sequence-stopped
102
- * @fires Panoramax.components.ui.Photo#pictures-navigation-changed
103
- * @fires Panoramax.components.ui.Photo#ready
104
- * @fires Panoramax.components.ui.Photo#annotations-toggled
105
- * @fires Panoramax.components.ui.Photo#annotation-click
106
- * @fires Panoramax.components.ui.Photo#annotation-focused
107
- * @fires Panoramax.components.ui.Photo#annotations-unfocused
108
- * @example
109
- * const psv = new Panoramax.components.ui.Photo(viewer, psvNode, {transitionDuration: 500})
110
- */
111
- export default class Photo extends PSViewer {
112
- constructor(parent, container, options = {}) {
113
- super({
114
- container,
115
- adapter: [PhotoAdapter, {
116
- showErrorTile: false,
117
- baseBlur: false,
118
- resolution: parent.isWidthSmall() ? 32 : 64,
119
- shouldGoFast: options?.shouldGoFast || (() => false),
120
- useXmpData: false,
121
- }],
122
- withCredentials: parent.api._getPSVWithCredentials(),
123
- requestHeaders: parent?.["fetch-options"]?.headers,
124
- panorama: BASE_PANORAMA,
125
- lang: parent._t.psv,
126
- minFov: 5,
127
- loadingTxt: " ",
128
- navbar: null,
129
- rendererParameters: {
130
- preserveDrawingBuffer: !parent.isWidthSmall(),
131
- },
132
- plugins: [
133
- [VirtualTourPlugin, {
134
- dataMode: "server",
135
- positionMode: "gps",
136
- renderMode: "3d",
137
- preload: true,
138
- getNode: () => {},
139
- transitionOptions: () => {},
140
- arrowsPosition: {
141
- linkOverlapAngle: Math.PI / 6,
142
- }
143
- }],
144
- [MarkersPlugin, {}],
145
- ...(options.plugins || [])
146
- ],
147
- ...keepOnlyPSVOptions(options)
148
- });
149
-
150
- this._parent = parent;
151
- this._options = options;
152
- container.classList.add("pnx-psv");
153
- this._shouldGoFast = options?.shouldGoFast || (() => false);
154
- this._transitionDuration = options?.transitionDuration || PSV_ANIM_DURATION;
155
- this._myVTour = this.getPlugin(VirtualTourPlugin);
156
- this._myVTour.datasource.nodeResolver = this._getNodeFromAPI.bind(this);
157
- this._myVTour.config.transitionOptions = this._psvNodeTransition.bind(this);
158
- this._clearArrows = this._myVTour.arrowsRenderer.clear.bind(this._myVTour.arrowsRenderer);
159
- this._myVTour.arrowsRenderer.clear = () => {};
160
- this._myMarkers = this.getPlugin(MarkersPlugin);
161
- this._annotationsVisible = this._options.displayAnnotations === true;
162
- this._drawingAnnotation = null;
163
- this._sequencePlaying = false;
164
- this._picturesNavigation = this._options.picturesNavigation || "any";
165
-
166
- // Cache to find sequence ID for a single picture
167
- this._picturesSequences = {};
168
-
169
- // Offer various custom events
170
- this._myVTour.addEventListener("enter-arrow", this._onEnterArrow.bind(this));
171
- this._myVTour.addEventListener("leave-arrow", this._onLeaveArrow.bind(this));
172
- this._myVTour.addEventListener("node-changed", this._onNodeChanged.bind(this));
173
- this._myMarkers.addEventListener("select-marker", this._onSelectMarker.bind(this));
174
- this.addEventListener("position-updated", this._onPositionUpdated.bind(this));
175
- this.addEventListener("zoom-updated", this._onZoomUpdated.bind(this));
176
- this.addEventListener("dblclick", this._onDoubleClick.bind(this));
177
- this._parent.addEventListener("select", this._onSelect.bind(this));
178
-
179
- // Fix for loader circle background not showing up
180
- this.loader.size = 150;
181
- this.loader.color = "rgba(61, 61, 61, 0.5)";
182
- this.loader.textColor = "rgba(255, 255, 255, 0.7)";
183
- this.loader.border = 5;
184
- this.loader.thickness = 10;
185
- this.loader.canvas.setAttribute("viewBox", "0 0 150 150");
186
- this.loader.__updateContent();
187
-
188
- // Handle initial parameters
189
- if(this._options.position && !this._parent.picture) {
190
- this.goToPosition(...this._options.position);
191
- }
192
- }
193
-
194
- /**
195
- * Calls API to retrieve a certain picture, then transforms into PSV format
196
- *
197
- * @private
198
- * @param {string} picId The picture UUID
199
- * @returns {Promise} Resolves on PSV node metadata
200
- * @memberof Panoramax.components.ui.Photo#
201
- */
202
- async _getNodeFromAPI(picId) {
203
- if(isNullId(picId)) { return BASE_PANORAMA_NODE; }
204
-
205
- const picApiResponse = await fetch(
206
- this._parent.getAPI().getPictureMetadataUrl(picId, this._picturesSequences[picId]),
207
- this._parent.getAPI()._getFetchOptions()
208
- );
209
- let metadata = await picApiResponse.json();
210
-
211
- if(metadata.features) { metadata = metadata.features.pop(); }
212
- if(!metadata || Object.keys(metadata).length === 0 || !picApiResponse.ok) {
213
- if(this._parent.loader) {
214
- this._parent.loader.dismiss(true, this._parent._t.pnx.error_pic);
215
- }
216
- throw new Error("Picture with ID " + picId + " was not found");
217
- }
218
-
219
- this._picturesSequences[picId] = metadata.collection;
220
- const node = apiFeatureToPSVNode(
221
- metadata,
222
- this._parent._t,
223
- this._parent._isInternetFast,
224
- this._picturesNavFilter.bind(this),
225
- this._parent.getAPI().cleanResourceURL.bind(this._parent.getAPI())
226
- );
227
- if(node?.sequence?.prevPic) { this._picturesSequences[node?.sequence?.prevPic] = metadata.collection; }
228
- if(node?.sequence?.nextPic) { this._picturesSequences[node?.sequence?.nextPic] = metadata.collection; }
229
-
230
- return node;
231
- }
232
-
233
- /**
234
- * PSV node transition handler
235
- * @param {*} toNode Next loading node
236
- * @param {*} [fromNode] Currently shown node (previous)
237
- * @param {*} [fromLink] Link clicked by user to go from current to next node
238
- * @private
239
- * @memberof Panoramax.components.ui.Photo#
240
- */
241
- _psvNodeTransition(toNode, fromNode, fromLink) {
242
- let nodeTransition = {};
243
-
244
- const animationDuration = this._shouldGoFast() ? 0 : Math.min(PSV_ANIM_DURATION, this._transitionDuration);
245
- const animated = animationDuration > 100;
246
- // eslint-disable-next-line eqeqeq
247
- const following = (fromLink || fromNode?.links.find(a => a.nodeId == toNode.id)) != null;
248
- const sameSequence = fromNode && toNode.sequence.id === fromNode.sequence.id;
249
- const fromNodeHeading = (fromNode?.properties?.["view:azimuth"] || 0) * (Math.PI / 180);
250
- const toNodeHeading = (toNode?.properties?.["view:azimuth"] || 0) * (Math.PI / 180);
251
- const toNodeRelHeading = getRelativeHeading(toNode) * (Math.PI / 180);
252
-
253
- this.setOption("maxFov", Math.min(toNode.horizontalFov * 3/4, 90));
254
-
255
- const forwardNoAnim = {
256
- showLoader: false,
257
- effect: "none",
258
- speed: 0,
259
- rotation: false,
260
- rotateTo: { pitch: 0, yaw: -toNodeRelHeading },
261
- zoomTo: PSV_DEFAULT_ZOOM
262
- };
263
-
264
- // Going to 360
265
- // eslint-disable-next-line eqeqeq
266
- if(toNode.horizontalFov == 360) {
267
- // No previous sequence -> Point to center + no animation
268
- if(!fromNode) {
269
- nodeTransition = forwardNoAnim;
270
- }
271
- // Has a previous sequence
272
- else {
273
- // Far away sequences -> Point to center + no animation
274
- if(getDistance(fromNode.gps, toNode.gps) >= 0.001) {
275
- nodeTransition = forwardNoAnim;
276
- }
277
- // Nearby sequences -> Keep orientation
278
- else {
279
- nodeTransition = {
280
- speed: animationDuration,
281
- effect: following && animated ? "fade" : "none",
282
- rotation: following && sameSequence && animated,
283
- rotateTo: this.getPosition()
284
- };
285
- // Constant direction related to North
286
- // NodeTransition.rotateTo.yaw += fromNodeHeading - toNodeHeading;
287
- }
288
- }
289
- }
290
- // Going to flat
291
- else {
292
- // Same sequence -> Point to center + animation if following pics + not vomiting
293
- if(sameSequence) {
294
- const fromYaw = this.getPosition().yaw;
295
- const fovMaxYaw = (fromNode.horizontalFov * (Math.PI / 180)) / 2;
296
- const keepZoomPos = fromYaw <= fovMaxYaw || fromYaw >= (2 * Math.PI - fovMaxYaw);
297
- const notTooMuchRotation = Math.abs(fromNodeHeading - toNodeHeading) <= Math.PI / 4;
298
- nodeTransition = {
299
- speed: animationDuration,
300
- effect: following && notTooMuchRotation && animated ? "fade" : "none",
301
- rotation: following && notTooMuchRotation && animated,
302
- rotateTo: keepZoomPos ? this.getPosition() : { pitch: 0, yaw: 0 },
303
- zoomTo: keepZoomPos ? this.getZoomLevel() : PSV_DEFAULT_ZOOM,
304
- };
305
- }
306
- // Different sequence -> Point to center + no animation
307
- else {
308
- nodeTransition = Object.assign(forwardNoAnim, {
309
- rotateTo: { pitch: 0, yaw: 0 },
310
- });
311
- }
312
- }
313
-
314
- if(nodeTransition.effect === "fade" && nodeTransition.speed >= 150) {
315
- setTimeout(this._clearArrows, nodeTransition.speed-100);
316
- }
317
- else {
318
- this._clearArrows();
319
- }
320
-
321
-
322
- /**
323
- * Event for picture starting to load
324
- *
325
- * @event Panoramax.components.ui.Photo#picture-loading
326
- * @type {CustomEvent}
327
- * @property {string} detail.picId The picture unique identifier
328
- * @property {number} detail.lon Longitude (WGS84)
329
- * @property {number} detail.lat Latitude (WGS84)
330
- * @property {number} detail.x New x position (in degrees, 0-360), corresponds to heading (0° = North, 90° = East, 180° = South, 270° = West)
331
- * @property {number} detail.y New y position (in degrees)
332
- * @property {number} detail.z New z position (0-100)
333
- * @property {boolean} detail.first True if first picture loaded
334
- */
335
- const event = new CustomEvent("picture-loading", {
336
- detail: {
337
- ...Object.assign({},
338
- this.getXYZ(),
339
- nodeTransition.rotateTo ? { x: (toNodeHeading + nodeTransition.rotateTo.yaw) * 180 / Math.PI } : null,
340
- nodeTransition.zoomTo ? { z: nodeTransition.zoomTo } : null
341
- ),
342
- picId: toNode.id,
343
- lon: toNode.gps[0],
344
- lat: toNode.gps[1],
345
- // eslint-disable-next-line eqeqeq
346
- first: this._parent._initParams?.getParentPostInit().picture == toNode.id,
347
- }
348
- });
349
- this.dispatchEvent(event);
350
-
351
- return nodeTransition;
352
- }
353
-
354
- /**
355
- * Event handler for PSV arrow hover.
356
- * It creates a custom event "picture-preview-started"
357
- * @private
358
- * @param {object} e The event data
359
- * @memberof Panoramax.components.ui.Photo#
360
- */
361
- _onEnterArrow(e) {
362
- const fromLink = e.link;
363
- const fromNode = e.node;
364
-
365
- // Find probable direction for previewed picture
366
- let direction;
367
- if(fromNode) {
368
- if(fromNode.horizontalFov === 360) {
369
- direction = (this.getPictureOriginalHeading() + this.getPosition().yaw * 180 / Math.PI) % 360;
370
- }
371
- else {
372
- direction = this.getPictureOriginalHeading();
373
- }
374
- }
375
-
376
- /**
377
- * Event for picture preview
378
- *
379
- * @event Panoramax.components.ui.Photo#picture-preview-started
380
- * @type {CustomEvent}
381
- * @property {string} detail.picId The picture ID
382
- * @property {number[]} detail.coordinates [x,y] coordinates
383
- * @property {number} detail.direction The theoretical picture orientation
384
- */
385
- const event = new CustomEvent("picture-preview-started", { detail: {
386
- picId: fromLink.nodeId,
387
- coordinates: fromLink.gps,
388
- direction,
389
- }});
390
- this.dispatchEvent(event);
391
- }
392
-
393
- /**
394
- * Event handler for PSV arrow end of hovering.
395
- * It creates a custom event "picture-preview-stopped"
396
- * @private
397
- * @param {object} e The event data
398
- * @memberof Panoramax.components.ui.Photo#
399
- */
400
- _onLeaveArrow(e) {
401
- const fromLink = e.link;
402
-
403
- /**
404
- * Event for end of picture preview
405
- * @event Panoramax.components.ui.Photo#picture-preview-stopped
406
- * @type {CustomEvent}
407
- * @property {string} detail.picId The picture ID
408
- */
409
- const event = new CustomEvent("picture-preview-stopped", { detail: {
410
- picId: fromLink.nodeId,
411
- }});
412
- this.dispatchEvent(event);
413
- }
414
-
415
- /**
416
- * Event handler for position update in PSV.
417
- * Allows to send a custom "view-rotated" event.
418
- * @private
419
- * @memberof Panoramax.components.ui.Photo#
420
- */
421
- _onPositionUpdated({position}) {
422
- const pos = positionToXYZ(position, this.getZoomLevel());
423
- pos.x += this.getPictureOriginalHeading();
424
- pos.x = pos.x % 360;
425
-
426
- /**
427
- * Event for viewer rotation
428
- * @event Panoramax.components.ui.Photo#view-rotated
429
- * @type {CustomEvent}
430
- * @property {number} detail.x New x position (in degrees, 0-360), corresponds to heading (0° = North, 90° = East, 180° = South, 270° = West)
431
- * @property {number} detail.y New y position (in degrees)
432
- * @property {number} detail.z New Z position (between 0 and 100)
433
- */
434
- const event = new CustomEvent("view-rotated", { detail: pos });
435
- this.dispatchEvent(event);
436
-
437
- this._onTilesStartLoading();
438
- }
439
-
440
- /**
441
- * Event handler for zoom updates in PSV.
442
- * Allows to send a custom "view-rotated" event.
443
- * @private
444
- * @memberof Panoramax.components.ui.Photo#
445
- */
446
- _onZoomUpdated({zoomLevel}) {
447
- const event = new CustomEvent("view-rotated", { detail: { ...this.getXY(), z: zoomLevel} });
448
- this.dispatchEvent(event);
449
-
450
- this._onTilesStartLoading();
451
- }
452
-
453
- /**
454
- * Event handler for double click
455
- * @private
456
- */
457
- _onDoubleClick() {
458
- this.unfocusAnnotation(false, true);
459
- }
460
-
461
- /**
462
- * Event handler for node change in PSV.
463
- * Allows to send a custom "picture-loaded" event.
464
- * @private
465
- * @memberof Panoramax.components.ui.Photo#
466
- */
467
- _onNodeChanged(e) {
468
- // Clean up clicked arrows
469
- for(let d of document.getElementsByClassName("pnx-psv-tour-arrows")) {
470
- d.classList.remove("pnx-clicked");
471
- }
472
-
473
- if(e.node.id) {
474
- // eslint-disable-next-line eqeqeq
475
- const isFirst = this._parent._initParams?.getParentPostInit().picture == e.node.id;
476
- this._parent.select(e.node?.sequence?.id, e.node.id);
477
- const picMeta = this.getPictureMetadata();
478
- if(!picMeta) {
479
- this.dispatchEvent(new CustomEvent("picture-loaded", {detail: {}}));
480
- return;
481
- }
482
- this._prevSequence = picMeta.sequence.id;
483
-
484
- /**
485
- * Event for picture load (low-resolution image is loaded)
486
- * @event Panoramax.components.ui.Photo#picture-loaded
487
- * @type {CustomEvent}
488
- * @property {string} detail.picId The picture unique identifier
489
- * @property {number} detail.lon Longitude (WGS84)
490
- * @property {number} detail.lat Latitude (WGS84)
491
- * @property {number} detail.x New x position (in degrees, 0-360), corresponds to heading (0° = North, 90° = East, 180° = South, 270° = West)
492
- * @property {number} detail.y New y position (in degrees)
493
- * @property {number} detail.z New z position (0-100)
494
- * @property {boolean} detail.first True if first picture loaded
495
- */
496
- const event = new CustomEvent("picture-loaded", {
497
- detail: {
498
- ...this.getXYZ(),
499
- picId: e.node.id,
500
- lon: picMeta.gps[0],
501
- lat: picMeta.gps[1],
502
- first: isFirst
503
- },
504
- });
505
- this.dispatchEvent(event);
506
-
507
- // Change download URL
508
- if(picMeta.panorama.hdUrl) {
509
- this.setOption("downloadUrl", picMeta.panorama.hdUrl);
510
- this.setOption("downloadName", e.node.id+".jpg");
511
- }
512
- else {
513
- this.setOption("downloadUrl", null);
514
- }
515
-
516
- // Show annotations
517
- if(this._annotationsVisible) {
518
- this.toggleAllAnnotations(true);
519
- }
520
- }
521
-
522
- this._onTilesStartLoading();
523
- }
524
-
525
- /**
526
- * Event handler for marker select
527
- * @memberof Panoramax.components.ui.Photo#
528
- * @private
529
- */
530
- _onSelectMarker(e) {
531
- if(!e.marker) { return; }
532
- if(e.marker.id?.startsWith("annotation-")) {
533
- // Check if there is no blocked focus on
534
- const ba = this.getSelectedAnnotations().find(a => this._myMarkers.markers["annotation-"+a]?.config?.data?.blockFocus);
535
- if(ba) { return; }
536
- this.focusOnAnnotation(e.marker.data.id);
537
- }
538
- }
539
-
540
- /**
541
- * Event handler for loading a new range of tiles
542
- * @memberof Panoramax.components.ui.Photo#
543
- * @private
544
- */
545
- _onTilesStartLoading() {
546
- if(this._tilesQueueTimer) {
547
- clearInterval(this._tilesQueueTimer);
548
- delete this._tilesQueueTimer;
549
- }
550
- this._tilesQueueTimer = setInterval(() => {
551
- if(Object.keys(this.adapter.queue.tasks).length === 0) {
552
- if(this._myVTour.state.currentNode) {
553
- /**
554
- * Event launched when all visible tiles of a picture are loaded
555
- * @event Panoramax.components.ui.Photo#picture-tiles-loaded
556
- * @type {CustomEvent}
557
- * @property {string} detail.picId The picture unique identifier
558
- */
559
- const event = new CustomEvent("picture-tiles-loaded", { detail: { picId: this._myVTour.state.currentNode.id }});
560
- this.dispatchEvent(event);
561
- }
562
- clearInterval(this._tilesQueueTimer);
563
- delete this._tilesQueueTimer;
564
- }
565
- }, 100);
566
- }
567
-
568
- /**
569
- * Access currently shown picture metadata
570
- * @memberof Panoramax.components.ui.Photo#
571
- * @returns {object} Picture metadata
572
- */
573
- getPictureMetadata() {
574
- if(isNullId(this._myVTour?.state?.currentNode?.id)) { return null; }
575
- return this._myVTour.state.currentNode ? Object.assign({}, this._myVTour.state.currentNode) : null;
576
- }
577
-
578
- /**
579
- * Get current picture ID, or loading picture ID if any.
580
- * @memberof Panoramax.components.ui.Photo#
581
- * @returns {string|null} Picture ID (current or loading), or null if none is selected.
582
- */
583
- getPictureId() {
584
- const id = this._myVTour?.state?.loadingNode || this._myVTour?.state?.currentNode?.id;
585
- return isNullId(id) ? null : id;
586
- }
587
-
588
- /**
589
- * Handler for select event.
590
- * @private
591
- * @memberof Panoramax.components.ui.Photo#
592
- */
593
- _onSelect(e) {
594
- if(!this._parent.getAPI()) {
595
- return (
596
- this._parent.onceAllAPIsReady || this._parent.onceAPIReady
597
- ).bind(this._parent)().then(() => this._onSelect(e));
598
- }
599
-
600
- if(e.detail.seqId) {
601
- this._picturesSequences[e.detail.picId] = e.detail.seqId;
602
- }
603
-
604
- if(e.detail.seqId && !e.detail.picId) {
605
- // Call API to find first picture ID in sequence
606
- this.loader.show();
607
- this._parent.getAPI().getSequenceItems(e.detail.seqId, null, null, 1)
608
- .then(data => {
609
- if(data?.features?.length > 0) {
610
- const picId = data.features[0].id;
611
- this._picturesSequences[picId] = e.detail.seqId;
612
- this._parent.select(e.detail.seqId, picId);
613
- }
614
- }).catch(e => this.showErrorOverlay(e, this._parent._t.pnx.error_pic, true));
615
- }
616
- else if(!e.detail.picId) {
617
- this._myVTour.setCurrentNode(null);
618
- this._myVTour.state.currentNode.id = null;
619
- this._myVTour.state.loadingNode = null;
620
- }
621
- else if(this.getPictureId() !== e.detail.picId) {
622
- this.loader.show();
623
- this._myVTour.setCurrentNode(e.detail.picId).catch(e => {
624
- this.showErrorOverlay(e, this._parent._t.pnx.error_pic, true);
625
- });
626
- }
627
- }
628
-
629
- /**
630
- * Displays next picture in current sequence (if any)
631
- * @memberof Panoramax.components.ui.Photo#
632
- * @throws {Error} If no picture is selected, or no next picture available
633
- */
634
- goToNextPicture() {
635
- if(!this.getPictureMetadata()) {
636
- throw new Error("No picture currently selected");
637
- }
638
-
639
- if(this._picturesNavigation === "pic") {
640
- throw new Error("Pictures navigation is locked to current picture");
641
- }
642
-
643
- const next = this.getPictureMetadata().sequence.nextPic;
644
- if(next) {
645
- this._parent.select(this.getPictureMetadata().sequence.id, next);
646
- }
647
- else {
648
- throw new Error("No next picture available");
649
- }
650
- }
651
-
652
- /**
653
- * Displays previous picture in current sequence (if any)
654
- * @memberof Panoramax.components.ui.Photo#
655
- * @throws {Error} If no picture is selected, or no previous picture available
656
- */
657
- goToPrevPicture() {
658
- if(!this.getPictureMetadata()) {
659
- throw new Error("No picture currently selected");
660
- }
661
-
662
- if(this._picturesNavigation === "pic") {
663
- throw new Error("Pictures navigation is locked to current picture");
664
- }
665
-
666
- const prev = this.getPictureMetadata().sequence.prevPic;
667
- if(prev) {
668
- this._parent.select(this.getPictureMetadata().sequence.id, prev);
669
- }
670
- else {
671
- throw new Error("No previous picture available");
672
- }
673
- }
674
-
675
- /**
676
- * Displays in viewer a picture near to given coordinates
677
- * @memberof Panoramax.components.ui.Photo#
678
- * @param {number} lat Latitude (WGS84)
679
- * @param {number} lon Longitude (WGS84)
680
- * @returns {Promise}
681
- * @fulfil {string} Picture ID if picture found
682
- * @reject {Error} If no picture found
683
- */
684
- async goToPosition(lat, lon) {
685
- if(this._picturesNavigation === "pic") {
686
- throw new Error("Pictures navigation is locked to current picture");
687
- }
688
-
689
- return await this._parent.getAPI().getPicturesAroundCoordinates(lat, lon)
690
- .then(res => {
691
- if(res.features.length > 0) {
692
- const f = res.features.pop();
693
- this._parent.select(
694
- f?.collection,
695
- f.id
696
- );
697
- return f.id;
698
- }
699
- else {
700
- return Promise.reject(new Error("No picture found nearby given coordinates"));
701
- }
702
- });
703
- }
704
-
705
- /**
706
- * Get 2D position of sphere currently shown to user
707
- * @memberof Panoramax.components.ui.Photo#
708
- * @returns {object} Position in format { x: heading in degrees (0° = North, 90° = East, 180° = South, 270° = West), y: top/bottom position in degrees (-90° = bottom, 0° = front, 90° = top) }
709
- */
710
- getXY() {
711
- const pos = positionToXYZ(this.getPosition());
712
- pos.x = (pos.x + this.getPictureOriginalHeading()) % 360;
713
- return pos;
714
- }
715
-
716
- /**
717
- * Get 3D position of sphere currently shown to user
718
- * @memberof Panoramax.components.ui.Photo#
719
- * @returns {object} Position in format { x: heading in degrees (0° = North, 90° = East, 180° = South, 270° = West), y: top/bottom position in degrees (-90° = bottom, 0° = front, 90° = top), z: zoom (0 = wide, 100 = zoomed in) }
720
- */
721
- getXYZ() {
722
- const pos = this.getXY();
723
- pos.z = this.getZoomLevel();
724
- return pos;
725
- }
726
-
727
- /**
728
- * Get capture orientation of current picture, based on its GPS.
729
- * @returns {number} Picture original heading in degrees (0 to 360°)
730
- * @memberof Panoramax.components.ui.Photo#
731
- */
732
- getPictureOriginalHeading() {
733
- return this.getPictureMetadata()?.properties?.["view:azimuth"] || 0;
734
- }
735
-
736
- /**
737
- * Computes the relative heading of currently selected picture.
738
- * This gives the angle of capture compared to sequence path (vehicle movement).
739
- * @memberof Panoramax.components.ui.Photo#
740
- * @returns {number} Relative heading in degrees (-180 to 180)
741
- */
742
- getPictureRelativeHeading() {
743
- let rel = this.getPictureOriginalHeading() - this.getRoadAbsoluteHeading();
744
- while (rel > 180) { rel = rel - 360; }
745
- while (rel < -180) { rel = rel + 360; }
746
- return rel;
747
- }
748
-
749
- /**
750
- * Computes the absolute heading of the road (ie sequence around selected picutre).
751
- * @memberof Panoramax.components.ui.Photo#
752
- * @returns {number} Absolute heading in degrees (0 to 360)
753
- */
754
- getRoadAbsoluteHeading() {
755
- return getRoadAbsoluteHeading(this.getPictureMetadata());
756
- }
757
-
758
- /**
759
- * Clears the Photo Sphere Viewer metadata cache.
760
- * It is useful when current picture or sequence has changed server-side after first load.
761
- * @memberof Panoramax.components.ui.Photo#
762
- */
763
- clearPictureMetadataCache() {
764
- const oldPicId = this.getPictureMetadata()?.id;
765
- const oldSeqId = this.getPictureMetadata()?.sequence?.id;
766
-
767
- // Force deletion of cached metadata in PSV
768
- this._myVTour.state.currentTooltip?.hide();
769
- this._myVTour.state.currentTooltip = null;
770
- this._myVTour.state.currentNode = null;
771
- this._myVTour.state.preload = {};
772
- this._myVTour.datasource.nodes = {};
773
-
774
- // Reload current picture if one was selected
775
- if(oldPicId) {
776
- this._parent.select(oldSeqId, oldPicId);
777
- }
778
- }
779
-
780
- /**
781
- * Change the shown position in picture
782
- * @memberof Panoramax.components.ui.Photo#
783
- * @param {number} x X position (in degrees)
784
- * @param {number} y Y position (in degrees)
785
- * @param {number} z Z position (0-100)
786
- */
787
- setXYZ(x, y, z) {
788
- const coords = xyzToPosition(x - this.getPictureOriginalHeading(), y, z);
789
- this.rotate({ yaw: coords.yaw, pitch: coords.pitch });
790
- this.zoom(coords.zoom);
791
- }
792
-
793
- /**
794
- * Enable or disable higher contrast on picture
795
- * @param {boolean} enable True to enable higher contrast
796
- * @memberof Panoramax.components.ui.Photo#
797
- */
798
- setHigherContrast(enable) {
799
- this.renderer.renderer.toneMapping = enable ? 3 : 0;
800
- this.renderer.renderer.toneMappingExposure = enable ? 2 : 1;
801
- this.needsUpdate();
802
- }
803
-
804
- /**
805
- * Get the duration of stay on a picture during a sequence play.
806
- * @returns {number} The duration (in milliseconds)
807
- * @memberof Panoramax.components.ui.Photo#
808
- */
809
- getTransitionDuration() {
810
- return this._transitionDuration;
811
- }
812
-
813
- /**
814
- * Changes the duration of stay on a picture during a sequence play.
815
- * @memberof Panoramax.components.ui.Photo#
816
- * @param {number} value The new duration (in milliseconds, between 100 and 3000)
817
- */
818
- setTransitionDuration(value) {
819
- value = parseFloat(value);
820
- if(value < 100 || value > PIC_MAX_STAY_DURATION) {
821
- throw new Error("Invalid transition duration (should be between 100 and "+PIC_MAX_STAY_DURATION+")");
822
- }
823
- this._transitionDuration = value;
824
-
825
- /**
826
- * Event for transition duration change
827
- * @event Panoramax.components.ui.Photo#transition-duration-changed
828
- * @type {CustomEvent}
829
- * @property {string} detail.duration New duration (in milliseconds)
830
- */
831
- const event = new CustomEvent("transition-duration-changed", { detail: { value } });
832
- this.dispatchEvent(event);
833
- }
834
-
835
- /** @private */
836
- setPanorama(path, options) {
837
- if(path.baseUrl.startsWith("data:")) { return Promise.resolve(); }
838
-
839
- const onFailure = e => this.showErrorOverlay(e, this._parent?._t.pnx.error_pic, true);
840
- try {
841
- return super.setPanorama(path, options).catch(onFailure);
842
- }
843
- catch(e) {
844
- onFailure(e);
845
- }
846
- }
847
-
848
- /**
849
- * Display an error message to user on screen
850
- * @param {object} e The initial error
851
- * @param {str} label The main error label to display
852
- * @param {boolean} dissmisable Is error dissmisable
853
- * @memberof Panoramax.components.ui.Photo#
854
- */
855
- showErrorOverlay(e, label, dissmisable) {
856
- if(this._parent?.loader.isVisible() || !this.overlay.isVisible()) {
857
- this._parent?.loader.dismiss(
858
- e,
859
- label,
860
- dissmisable ? () => {
861
- this._parent?.loader.dismiss();
862
- this.overlay.hide();
863
- } : undefined
864
- );
865
- }
866
- else {
867
- console.error(e);
868
- this.overlay.show({
869
- image: `<img style="width: 200px" src="${LogoDead}" alt="" />`,
870
- title: this._parent?._t.pnx.error,
871
- text: label + "<br />" + this._parent?._t.pnx.error_click,
872
- dissmisable,
873
- });
874
- }
875
- }
876
-
877
- /**
878
- * Goes continuously to next picture in sequence as long as possible
879
- * @memberof Panoramax.components.ui.Photo#
880
- */
881
- playSequence() {
882
- this._sequencePlaying = true;
883
- this.container.classList.add("pnx-psv-playing");
884
-
885
- /**
886
- * Event for sequence starting to play
887
- * @event Panoramax.components.ui.Photo#sequence-playing
888
- * @type {CustomEvent}
889
- */
890
- const event = new Event("sequence-playing", {bubbles: true, composed: true});
891
- this.dispatchEvent(event);
892
-
893
- const nextPicturePlay = () => {
894
- if(this._sequencePlaying) {
895
- this.addEventListener("picture-loaded", () => {
896
- this._playTimer = setTimeout(() => {
897
- nextPicturePlay();
898
- }, this.getTransitionDuration());
899
- }, { once: true });
900
-
901
- try {
902
- this.goToNextPicture();
903
- }
904
- catch(e) {
905
- this.stopSequence();
906
- }
907
- }
908
- };
909
-
910
- // Stop playing if user clicks on image
911
- this.addEventListener("click", () => this.stopSequence());
912
-
913
- nextPicturePlay();
914
- }
915
-
916
- /**
917
- * Stops playing current sequence
918
- * @memberof Panoramax.components.ui.Photo#
919
- */
920
- stopSequence() {
921
- this._sequencePlaying = false;
922
- this.container.classList.remove("pnx-psv-playing");
923
-
924
- // Next picture timer is pending
925
- if(this._playTimer) {
926
- clearTimeout(this._playTimer);
927
- delete this._playTimer;
928
- }
929
-
930
- // Force refresh of PSV to eventually load tiles
931
- this.forceRefresh();
932
-
933
- /**
934
- * Event for sequence stopped playing
935
- * @event Panoramax.components.ui.Photo#sequence-stopped
936
- * @type {CustomEvent}
937
- */
938
- const event = new Event("sequence-stopped", {bubbles: true, composed: true});
939
- this.dispatchEvent(event);
940
- }
941
-
942
- /**
943
- * Is there any sequence being played right now ?
944
- * @memberof Panoramax.components.ui.Photo#
945
- * @returns {boolean} True if sequence is playing
946
- */
947
- isSequencePlaying() {
948
- return this._sequencePlaying;
949
- }
950
-
951
- /**
952
- * Starts/stops the reading of pictures in a sequence
953
- * @memberof Panoramax.components.ui.Photo#
954
- */
955
- toggleSequencePlaying() {
956
- if(this.isSequencePlaying()) {
957
- this.stopSequence();
958
- }
959
- else {
960
- this.playSequence();
961
- }
962
- }
963
-
964
- /**
965
- * Get current pictures navigation mode.
966
- * @returns {string} The picture navigation mode ("any": no restriction, "seq": only pictures in same sequence, "pic": only selected picture)
967
- * @memberof Panoramax.components.ui.Photo#
968
- */
969
- getPicturesNavigation() {
970
- return this._picturesNavigation;
971
- }
972
-
973
- /**
974
- * Switch the allowed navigation between pictures.
975
- * @param {string} pn The picture navigation mode ("any": no restriction, "seq": only pictures in same sequence, "pic": only selected picture)
976
- * @memberof Panoramax.components.ui.Photo#
977
- */
978
- setPicturesNavigation(pn) {
979
- if(pn === "none") { pn = "pic"; }
980
- this._picturesNavigation = pn;
981
-
982
- // Immediate switch of navigation arrows
983
- const arrows = this.container.querySelector(".psv-virtual-tour-arrows");
984
- if(arrows) {
985
- arrows.style.display = pn === "pic" ? "none" : null;
986
- }
987
-
988
- /**
989
- * Event for pictures navigation mode change
990
- * @event Panoramax.components.ui.Photo#pictures-navigation-changed
991
- * @type {CustomEvent}
992
- * @property {string} detail.value New mode (any, pic, seq)
993
- */
994
- const event = new CustomEvent("pictures-navigation-changed", { detail: { value: pn } });
995
- this.dispatchEvent(event);
996
- }
997
-
998
- /**
999
- * Filter function
1000
- * @param {object} link A STAC next/prev/related link definition
1001
- * @returns {boolean} True if link should be kept
1002
- * @private
1003
- */
1004
- _picturesNavFilter(link) {
1005
- switch(this._picturesNavigation) {
1006
- case "seq":
1007
- return ["next", "prev"].includes(link.rel);
1008
- case "pic":
1009
- case "none":
1010
- return false;
1011
- case "any":
1012
- default:
1013
- return true;
1014
- }
1015
- }
1016
-
1017
- /**
1018
- * Are there any picture annotations shown ?
1019
- * @returns {boolean} True if annotations are visible
1020
- * @memberof Panoramax.components.ui.Photo#
1021
- */
1022
- areAnnotationsVisible() {
1023
- return this._annotationsVisible;
1024
- }
1025
-
1026
- /**
1027
- * Get all selected annotations
1028
- * @memberof Panoramax.components.ui.Photo#
1029
- * @return {string[]} List of selected annotations IDs
1030
- */
1031
- getSelectedAnnotations() {
1032
- return Object.keys(this._myMarkers.markers)
1033
- .filter(id => id.startsWith("annotation-") && this._myMarkers.markers[id]?.config?.data?.selected)
1034
- .map(id => id.replace("annotation-", ""));
1035
- }
1036
-
1037
- /**
1038
- * Toggle visibility of picture annotations
1039
- * @param {boolean} visible True to make visible, false to hide
1040
- * @memberof Panoramax.components.ui.Photo#
1041
- */
1042
- toggleAllAnnotations(visible) {
1043
- const meta = this.getPictureMetadata();
1044
- const skipEvent = this._annotationsVisible === visible;
1045
- if(!meta) {
1046
- this._myMarkers.clearMarkers();
1047
- throw new Error("No picture currently selected");
1048
- }
1049
-
1050
- if(!visible) { this._myMarkers.clearMarkers(); }
1051
- else {
1052
- let annotations = meta.properties.annotations || [];
1053
- if(annotations?.length === 0) { console.warn("No annotation available on picture", meta.id); }
1054
-
1055
- annotations = annotations.map(a => {
1056
- // Get original HD picture dimensions
1057
- const origPicDim = meta.properties["pers:interior_orientation"].sensor_array_dimensions;
1058
-
1059
- if(!origPicDim) {
1060
- console.warn("Picture lacks pers:interior_orientation.sensor_array_dimensions property, can't compute marker");
1061
- return null;
1062
- }
1063
-
1064
- const shape = a.shape.coordinates.map(c1 => c1.map(c2 => {
1065
- const crop = meta.panorama?.cropData;
1066
- const tc = {
1067
- textureX: c2[0] + (crop?.croppedX || 0),
1068
- textureY: c2[1] + (crop?.croppedY || 0)
1069
- };
1070
- return this.dataHelper.textureCoordsToSphericalCoords(tc);
1071
- }));
1072
-
1073
- return {
1074
- id: `annotation-${a.id}`,
1075
- polygon: shape,
1076
- data: { id: a.id },
1077
- className: "pnx-psv-annotation",
1078
- svgStyle: {
1079
- stroke: "var(--orange)",
1080
- strokeWidth: "3px",
1081
- fill: "var(--orange-transparent)",
1082
- cursor: "pointer",
1083
- },
1084
- tooltip: this._parent._t.pnx.semantics_annotation_tooltip,
1085
- };
1086
- });
1087
- this._myMarkers.setMarkers(annotations);
1088
- }
1089
-
1090
- this._annotationsVisible = visible;
1091
-
1092
- /**
1093
- * Event for pictures annotation visibility change
1094
- * @event Panoramax.components.ui.Photo#annotations-toggled
1095
- * @type {CustomEvent}
1096
- * @property {boolean} detail.visible True if they are visible
1097
- */
1098
- if(!skipEvent) {
1099
- this.dispatchEvent(new CustomEvent("annotations-toggled", { detail: { visible } }));
1100
- }
1101
- }
1102
-
1103
- /**
1104
- * Make view centered and zoomed on given annotation.
1105
- * @param {string} id The annotation UUID
1106
- * @param {boolean} [blockFocus=false] Set to true to disable user unselect
1107
- * @param {boolean} [skipEvent=false] Set to true to avoid launching annotation-focused event
1108
- * @memberof Panoramax.components.ui.Photo#
1109
- */
1110
- focusOnAnnotation(id, blockFocus = false, skipEvent = false) {
1111
- // Check if given annotation is not already focused
1112
- const selected = this.getSelectedAnnotations();
1113
- if(id && selected.length === 1 && selected[0] === id) { return; }
1114
-
1115
- // Show annotations if they are hidden
1116
- if(!this.areAnnotationsVisible()) { this.toggleAllAnnotations(true); }
1117
- this.unfocusAnnotation(true);
1118
-
1119
- // Check if annotations exists
1120
- const annotationId = `annotation-${id}`;
1121
- if(!this._myMarkers.markers[annotationId]) {
1122
- // Wait for it to be visible
1123
- setTimeout(() => this.focusOnAnnotation(id, blockFocus, skipEvent), 100);
1124
- return;
1125
- }
1126
-
1127
- this._myMarkers.updateMarker({
1128
- id: annotationId,
1129
- svgStyle: {
1130
- stroke: "var(--red)",
1131
- strokeWidth: "3px",
1132
- fill: "var(--red-transparent)",
1133
- },
1134
- data: {
1135
- selected: true,
1136
- blockFocus
1137
- }
1138
- });
1139
- this._myMarkers.gotoMarker(annotationId, 0);
1140
- this.zoom(65);
1141
-
1142
- /**
1143
- * Event launched on annotation focus
1144
- * @event Panoramax.components.ui.Photo#annotation-focused
1145
- * @type {CustomEvent}
1146
- * @property {string} detail.annotationId The annotation UUID
1147
- */
1148
- if(!skipEvent) {
1149
- const event = new CustomEvent("annotation-focused", { detail: { annotationId: id }});
1150
- this.dispatchEvent(event);
1151
- }
1152
- }
1153
-
1154
- /**
1155
- * Remove focus styling on annotations.
1156
- * @memberof Panoramax.components.ui.Photo#
1157
- * @param {boolean} [skipEvent=false] Set to true to avoid launching annotations-unfocused event
1158
- * @param {boolean} [skipBlocked=false] Set to true to not unfocus blocked annotations
1159
- */
1160
- unfocusAnnotation(skipEvent = false, skipBlocked = false) {
1161
- const selectedAnnotations = this.getSelectedAnnotations();
1162
-
1163
- if(selectedAnnotations.length > 0) {
1164
- let allBlocked = 0;
1165
- selectedAnnotations.forEach(id => {
1166
- const aid = "annotation-"+id;
1167
- if(skipBlocked && this._myMarkers.markers[aid]?.config?.data?.blockFocus) {
1168
- allBlocked++;
1169
- return;
1170
- }
1171
- this._myMarkers.updateMarker({
1172
- id: aid,
1173
- svgStyle: {
1174
- stroke: "var(--orange)",
1175
- strokeWidth: "3px",
1176
- fill: "var(--orange-transparent)",
1177
- },
1178
- data: {
1179
- selected: false,
1180
- blockFocus: false,
1181
- }
1182
- });
1183
- });
1184
-
1185
- if(!skipEvent && allBlocked < selectedAnnotations.length) {
1186
- /**
1187
- * Event for pictures annotation unfocus
1188
- * @event Panoramax.components.ui.Photo#annotations-unfocused
1189
- * @type {Event}
1190
- */
1191
- this.dispatchEvent(new Event("annotations-unfocused"));
1192
- }
1193
- }
1194
- }
1195
-
1196
- /**
1197
- * Starts the drawing of a user-defined annotation.
1198
- */
1199
- startDrawAnnotation() {
1200
- this.toggleAllAnnotations(false);
1201
- this._drawingAnnotation = [];
1202
- this._drawingMoves = 0;
1203
-
1204
- // Preview rectangle
1205
- this._myMarkers.addMarker({
1206
- id: DRAW_ANNOT_RECT,
1207
- polygon: [{yaw: 0, pitch: 0}, {yaw: 0, pitch: 0}, {yaw: 0, pitch: 0}],
1208
- svgStyle: {
1209
- fill: DRAW_ANNOT_COLORS.RECT_FILL,
1210
- stroke: DRAW_ANNOT_COLORS.RECT_STROKE,
1211
- strokeWidth: "2px"
1212
- },
1213
- visible: false,
1214
- });
1215
-
1216
- // First point
1217
- this._myMarkers.addMarker({
1218
- id: DRAW_ANNOT_1ST,
1219
- circle: 5,
1220
- position: {yaw: 0, pitch: 0},
1221
- style: {
1222
- cursor: "pointer",
1223
- },
1224
- svgStyle: {
1225
- fill: DRAW_ANNOT_COLORS.CORNER_FILL,
1226
- stroke: DRAW_ANNOT_COLORS.CORNER_STROKE,
1227
- strokeWidth: "3px"
1228
- }
1229
- });
1230
-
1231
- // Second point
1232
- this._myMarkers.addMarker({
1233
- id: DRAW_ANNOT_2ND,
1234
- circle: 5,
1235
- position: {yaw: 0, pitch: 0},
1236
- style: {
1237
- cursor: "pointer",
1238
- },
1239
- svgStyle: {
1240
- fill: DRAW_ANNOT_COLORS.CORNER_FILL,
1241
- stroke: DRAW_ANNOT_COLORS.CORNER_STROKE,
1242
- strokeWidth: "3px"
1243
- },
1244
- visible: false,
1245
- });
1246
-
1247
- // First vertex move handler
1248
- const firstMove = e => {
1249
- const pos = this.getPositionFromEvent(e);
1250
- if(pos && this._myMarkers.markers[DRAW_ANNOT_1ST]) {
1251
- this._myMarkers.updateMarker({
1252
- id: DRAW_ANNOT_1ST,
1253
- position: pos,
1254
- style: { cursor: "grab" },
1255
- });
1256
-
1257
- if(this._drawingAnnotation.length === 2 && this._myMarkers.markers[DRAW_ANNOT_RECT]) {
1258
- this._myMarkers.updateMarker({
1259
- id: DRAW_ANNOT_RECT,
1260
- polygon: [
1261
- pos,
1262
- { yaw: pos.yaw, pitch: this._drawingAnnotation[1].pitch },
1263
- this._drawingAnnotation[1],
1264
- { yaw: this._drawingAnnotation[1].yaw, pitch: pos.pitch }
1265
- ],
1266
- });
1267
- }
1268
- }
1269
- };
1270
-
1271
- // Second vertex move handler
1272
- const secMove = e => {
1273
- const pos = this.getPositionFromEvent(e);
1274
- if(pos && this._myMarkers.markers[DRAW_ANNOT_2ND]) {
1275
- this._myMarkers.updateMarker({
1276
- id: DRAW_ANNOT_2ND,
1277
- position: pos,
1278
- style: { cursor: "grab" },
1279
- });
1280
-
1281
- if(this._drawingAnnotation.length >= 1 && this._myMarkers.markers[DRAW_ANNOT_RECT]) {
1282
- this._myMarkers.updateMarker({
1283
- id: DRAW_ANNOT_RECT,
1284
- polygon: [
1285
- this._drawingAnnotation[0],
1286
- { yaw: this._drawingAnnotation[0].yaw, pitch: pos.pitch },
1287
- pos,
1288
- { yaw: pos.yaw, pitch: this._drawingAnnotation[0].pitch }
1289
- ],
1290
- });
1291
- }
1292
- }
1293
- };
1294
-
1295
- this._drawListeners = [firstMove, secMove];
1296
- this.parent.addEventListener("mousemove", firstMove);
1297
- this._drawingMoves = 1;
1298
-
1299
- // Handle click
1300
- this._drawListenerClick = e => {
1301
- let sendRectEvent = false;
1302
-
1303
- // Click on first vertex
1304
- if(this._drawingMoves !== 2 && e.marker.id === DRAW_ANNOT_1ST) {
1305
- // First time ? Start moving second vertex
1306
- if(this._drawingAnnotation.length === 0) {
1307
- this._drawingAnnotation.push({ yaw: e.marker.config.position.yaw, pitch: e.marker.config.position.pitch });
1308
- this.parent.removeEventListener("mousemove", firstMove);
1309
- this._myMarkers.updateMarker({
1310
- id: DRAW_ANNOT_1ST,
1311
- style: { cursor: "pointer" },
1312
- });
1313
- this._myMarkers.updateMarker({
1314
- id: DRAW_ANNOT_2ND,
1315
- visible: true,
1316
- position: { yaw: e.marker.config.position.yaw, pitch: e.marker.config.position.pitch },
1317
- });
1318
- this._myMarkers.updateMarker({ id: DRAW_ANNOT_RECT, visible: true });
1319
- this.parent.addEventListener("mousemove", secMove);
1320
- this._drawingMoves = 2;
1321
- }
1322
- // Not moving ? start moving
1323
- else if(this._drawingMoves === 0) {
1324
- this.parent.addEventListener("mousemove", firstMove);
1325
- this._drawingMoves = 1;
1326
- }
1327
- // Moving ? Update 1st rectangle coordinates
1328
- else {
1329
- this._drawingAnnotation[0] = { yaw: e.marker.config.position.yaw, pitch: e.marker.config.position.pitch };
1330
- this.parent.removeEventListener("mousemove", firstMove);
1331
- this._myMarkers.updateMarker({
1332
- id: DRAW_ANNOT_1ST,
1333
- style: { cursor: "pointer" },
1334
- });
1335
- this._drawingMoves = 0;
1336
- sendRectEvent = true;
1337
- }
1338
- }
1339
- // Click on second vertex
1340
- else if(this._drawingMoves !== 1 && e.marker.id === DRAW_ANNOT_2ND) {
1341
- // First time ? Fix rectangle
1342
- if(this._drawingAnnotation.length === 1) {
1343
- this._drawingAnnotation.push({ yaw: e.marker.config.position.yaw, pitch: e.marker.config.position.pitch });
1344
- this.parent.removeEventListener("mousemove", secMove);
1345
- this._myMarkers.updateMarker({
1346
- id: DRAW_ANNOT_2ND,
1347
- style: { cursor: "pointer" },
1348
- });
1349
- this._drawingMoves = 0;
1350
- sendRectEvent = true;
1351
- }
1352
- // Vertex moving ? Update 2nd rectangle coordinates
1353
- else if(this._drawingMoves === 2) {
1354
- this._drawingAnnotation[1] = { yaw: e.marker.config.position.yaw, pitch: e.marker.config.position.pitch };
1355
- this.parent.removeEventListener("mousemove", secMove);
1356
- this._myMarkers.updateMarker({
1357
- id: DRAW_ANNOT_2ND,
1358
- style: { cursor: "pointer" },
1359
- });
1360
- this._drawingMoves = 0;
1361
- sendRectEvent = true;
1362
- }
1363
- // Otherwise, start moving vertex
1364
- else {
1365
- this.parent.addEventListener("mousemove", secMove);
1366
- this._drawingMoves = 2;
1367
- }
1368
- }
1369
-
1370
- // Send valid rectangle through event
1371
- if(sendRectEvent) {
1372
- // Get texture coordinates (corresponding to a full 360° sphere)
1373
- const minxy = this.dataHelper.sphericalCoordsToTextureCoords(this._drawingAnnotation[0]);
1374
- const maxxy = this.dataHelper.sphericalCoordsToTextureCoords(this._drawingAnnotation[1]);
1375
- let shape = [
1376
- minxy.textureX,
1377
- minxy.textureY,
1378
- maxxy.textureX,
1379
- maxxy.textureY
1380
- ];
1381
-
1382
- // Re-compute original texture coordinates (flat or cropped images)
1383
- const crop = this.getPictureMetadata()?.panorama?.cropData;
1384
- if(crop) {
1385
- shape[0] -= crop.croppedX;
1386
- shape[1] -= crop.croppedY;
1387
- shape[2] -= crop.croppedX;
1388
- shape[3] -= crop.croppedY;
1389
-
1390
- // Check if annotations doesn't go outside of picture
1391
- if(shape[0] < 0 || shape[0] > crop.croppedWidth) { shape = null; }
1392
- else if(shape[1] < 0 || shape[1] > crop.croppedHeight) { shape = null; }
1393
- else if(shape[2] < 0 || shape[2] > crop.croppedWidth) { shape = null; }
1394
- else if(shape[3] < 0 || shape[3] > crop.croppedHeight) { shape = null; }
1395
- }
1396
-
1397
- // Check coordinates order
1398
- if(shape[0] >= shape[2] && shape[1] >= shape[3]) {
1399
- let maxx = shape[0];
1400
- let minx = shape[2];
1401
- let maxy = shape[1];
1402
- let miny = shape[3];
1403
- shape = [minx, miny, maxx, maxy];
1404
- }
1405
-
1406
- /**
1407
- * Event for annotation drawn
1408
- *
1409
- * @event Panoramax.components.ui.Photo#annotation-drawn
1410
- * @type {CustomEvent}
1411
- * @property {string} detail.shape The annotation geometry, or null if latest draw is invalid
1412
- */
1413
- const event = new CustomEvent("annotation-drawn", {
1414
- detail: { shape }
1415
- });
1416
- this.dispatchEvent(event);
1417
- }
1418
- };
1419
- this._myMarkers.addEventListener("select-marker", this._drawListenerClick);
1420
- }
1421
-
1422
- /**
1423
- * Keeps currently drawn annotation, but without possibility to edit it.
1424
- * Useful for previewing during tag editing.
1425
- */
1426
- fixDrawAnnotation() {
1427
- [DRAW_ANNOT_1ST, DRAW_ANNOT_2ND].forEach(mid => {
1428
- try { this._myMarkers.removeMarker(mid); }
1429
- catch(e) {}
1430
- });
1431
- (this._drawListeners || []).forEach(l => this.parent.removeEventListener("mousemove", l));
1432
- this._myMarkers.removeEventListener("select-marker", this._drawListenerClick);
1433
- this._myMarkers.updateMarker({
1434
- id: DRAW_ANNOT_RECT,
1435
- svgStyle: {
1436
- stroke: "var(--red)",
1437
- strokeWidth: "3px",
1438
- fill: "var(--red-transparent)",
1439
- cursor: "pointer",
1440
- },
1441
- });
1442
-
1443
- this._drawingMoves = 0;
1444
- }
1445
-
1446
- /**
1447
- * Removes artifacts from a previous user drawing.
1448
- */
1449
- stopDrawAnnotation() {
1450
- [DRAW_ANNOT_1ST, DRAW_ANNOT_2ND, DRAW_ANNOT_RECT].forEach(mid => {
1451
- try { this._myMarkers.removeMarker(mid); }
1452
- catch(e) {}
1453
- });
1454
- (this._drawListeners || []).forEach(l => this.parent.removeEventListener("mousemove", l));
1455
- this._myMarkers.removeEventListener("select-marker", this._drawListenerClick);
1456
-
1457
- delete this._drawingMoves;
1458
- this._drawingAnnotation = null;
1459
- }
1460
-
1461
- /**
1462
- * Computes yaw/pitch based on MouseEvent with clientX/clientY data.
1463
- * @param {Event} e The mouse event
1464
- * @returns {object} { yaw, pitch } or null if can't compute position.
1465
- */
1466
- getPositionFromEvent(e) {
1467
- const boundingRect = this.container.getBoundingClientRect();
1468
- const viewerX = e.clientX - boundingRect.left;
1469
- const viewerY = e.clientY - boundingRect.top;
1470
- const intersections = this.renderer.getIntersections({ x: viewerX, y: viewerY });
1471
- const sphereIntersection = intersections.find(i => i.object.userData[CONSTANTS.VIEWER_DATA]);
1472
- if(!sphereIntersection) { return; }
1473
- return this.dataHelper.vector3ToSphericalCoords(sphereIntersection.point);
1474
- }
1475
-
1476
- /**
1477
- * Force reload of texture and tiles.
1478
- * @memberof Panoramax.components.ui.Photo#
1479
- */
1480
- forceRefresh() {
1481
- const cn = this._myVTour.getCurrentNode();
1482
-
1483
- // Refresh mode for flat pictures
1484
- if(cn && cn.panorama.baseUrl !== cn?.panorama?.origBaseUrl) {
1485
- const prevZoom = this.getZoomLevel();
1486
- const prevPos = this.getPosition();
1487
- this._myVTour.state.currentNode = null;
1488
- this._myVTour.setCurrentNode(cn.id, {
1489
- zoomTo: prevZoom,
1490
- rotateTo: prevPos,
1491
- fadeIn: false,
1492
- speed: 0,
1493
- rotation: false,
1494
- });
1495
- }
1496
-
1497
- // Refresh mode for 360 pictures
1498
- if(cn && cn.panorama.rows > 1) {
1499
- this.adapter.__refresh();
1500
- }
1501
- }
1502
- }