@panoramax/web-viewer 3.0.2-develop-a8ea8e60
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/.dockerignore +6 -0
- package/.gitlab-ci.yml +71 -0
- package/CHANGELOG.md +428 -0
- package/CODE_OF_CONDUCT.md +134 -0
- package/Dockerfile +14 -0
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/build/editor.html +1 -0
- package/build/index.css +36 -0
- package/build/index.css.map +1 -0
- package/build/index.html +1 -0
- package/build/index.js +25 -0
- package/build/index.js.map +1 -0
- package/build/map.html +1 -0
- package/build/viewer.html +1 -0
- package/config/env.js +104 -0
- package/config/getHttpsConfig.js +66 -0
- package/config/getPackageJson.js +25 -0
- package/config/jest/babelTransform.js +29 -0
- package/config/jest/cssTransform.js +14 -0
- package/config/jest/fileTransform.js +40 -0
- package/config/modules.js +134 -0
- package/config/paths.js +72 -0
- package/config/pnpTs.js +35 -0
- package/config/webpack/persistentCache/createEnvironmentHash.js +9 -0
- package/config/webpack.config.js +885 -0
- package/config/webpackDevServer.config.js +127 -0
- package/docs/01_Start.md +149 -0
- package/docs/02_Usage.md +828 -0
- package/docs/03_URL_settings.md +140 -0
- package/docs/04_Advanced_examples.md +214 -0
- package/docs/05_Compatibility.md +85 -0
- package/docs/09_Develop.md +62 -0
- package/docs/90_Releases.md +27 -0
- package/docs/images/class_diagram.drawio +129 -0
- package/docs/images/class_diagram.jpg +0 -0
- package/docs/images/screenshot.jpg +0 -0
- package/mkdocs.yml +45 -0
- package/package.json +254 -0
- package/public/editor.html +54 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +59 -0
- package/public/map.html +53 -0
- package/public/viewer.html +67 -0
- package/scripts/build.js +217 -0
- package/scripts/start.js +176 -0
- package/scripts/test.js +52 -0
- package/src/Editor.css +37 -0
- package/src/Editor.js +359 -0
- package/src/StandaloneMap.js +114 -0
- package/src/Viewer.css +203 -0
- package/src/Viewer.js +1186 -0
- package/src/components/CoreView.css +64 -0
- package/src/components/CoreView.js +159 -0
- package/src/components/Loader.css +56 -0
- package/src/components/Loader.js +111 -0
- package/src/components/Map.css +65 -0
- package/src/components/Map.js +841 -0
- package/src/components/Photo.css +36 -0
- package/src/components/Photo.js +687 -0
- package/src/img/arrow_360.svg +14 -0
- package/src/img/arrow_flat.svg +11 -0
- package/src/img/arrow_triangle.svg +10 -0
- package/src/img/arrow_turn.svg +9 -0
- package/src/img/bg_aerial.jpg +0 -0
- package/src/img/bg_streets.jpg +0 -0
- package/src/img/loader_base.jpg +0 -0
- package/src/img/loader_hd.jpg +0 -0
- package/src/img/logo_dead.svg +91 -0
- package/src/img/marker.svg +17 -0
- package/src/img/marker_blue.svg +20 -0
- package/src/img/switch_big.svg +44 -0
- package/src/img/switch_mini.svg +48 -0
- package/src/index.js +10 -0
- package/src/translations/de.json +163 -0
- package/src/translations/en.json +164 -0
- package/src/translations/eo.json +6 -0
- package/src/translations/es.json +164 -0
- package/src/translations/fi.json +1 -0
- package/src/translations/fr.json +164 -0
- package/src/translations/hu.json +133 -0
- package/src/translations/nl.json +1 -0
- package/src/translations/zh_Hant.json +136 -0
- package/src/utils/API.js +709 -0
- package/src/utils/Exif.js +198 -0
- package/src/utils/I18n.js +75 -0
- package/src/utils/Map.js +382 -0
- package/src/utils/PhotoAdapter.js +45 -0
- package/src/utils/Utils.js +568 -0
- package/src/utils/Widgets.js +477 -0
- package/src/viewer/URLHash.js +334 -0
- package/src/viewer/Widgets.css +711 -0
- package/src/viewer/Widgets.js +1196 -0
- package/tests/Editor.test.js +125 -0
- package/tests/StandaloneMap.test.js +44 -0
- package/tests/Viewer.test.js +363 -0
- package/tests/__snapshots__/Editor.test.js.snap +300 -0
- package/tests/__snapshots__/StandaloneMap.test.js.snap +30 -0
- package/tests/__snapshots__/Viewer.test.js.snap +195 -0
- package/tests/components/CoreView.test.js +91 -0
- package/tests/components/Loader.test.js +38 -0
- package/tests/components/Map.test.js +230 -0
- package/tests/components/Photo.test.js +335 -0
- package/tests/components/__snapshots__/Loader.test.js.snap +15 -0
- package/tests/components/__snapshots__/Map.test.js.snap +767 -0
- package/tests/components/__snapshots__/Photo.test.js.snap +205 -0
- package/tests/data/Map_geocoder_ban.json +36 -0
- package/tests/data/Map_geocoder_nominatim.json +56 -0
- package/tests/data/Viewer_pictures_1.json +148 -0
- package/tests/setupTests.js +5 -0
- package/tests/utils/API.test.js +906 -0
- package/tests/utils/Exif.test.js +124 -0
- package/tests/utils/I18n.test.js +28 -0
- package/tests/utils/Map.test.js +105 -0
- package/tests/utils/Utils.test.js +300 -0
- package/tests/utils/Widgets.test.js +107 -0
- package/tests/utils/__snapshots__/API.test.js.snap +132 -0
- package/tests/utils/__snapshots__/Exif.test.js.snap +43 -0
- package/tests/utils/__snapshots__/Map.test.js.snap +48 -0
- package/tests/utils/__snapshots__/Utils.test.js.snap +41 -0
- package/tests/utils/__snapshots__/Widgets.test.js.snap +44 -0
- package/tests/viewer/URLHash.test.js +537 -0
- package/tests/viewer/Widgets.test.js +127 -0
- package/tests/viewer/__snapshots__/URLHash.test.js.snap +98 -0
- package/tests/viewer/__snapshots__/Widgets.test.js.snap +393 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
import "./Photo.css";
|
|
2
|
+
import LoaderImgBase from "../img/loader_base.jpg";
|
|
3
|
+
import LogoDead from "../img/logo_dead.svg";
|
|
4
|
+
import {
|
|
5
|
+
getDistance, positionToXYZ, xyzToPosition,
|
|
6
|
+
apiFeatureToPSVNode, getRelativeHeading,
|
|
7
|
+
} from "../utils/Utils";
|
|
8
|
+
|
|
9
|
+
// Photo Sphere Viewer imports
|
|
10
|
+
import "@photo-sphere-viewer/core/index.css";
|
|
11
|
+
import "@photo-sphere-viewer/virtual-tour-plugin/index.css";
|
|
12
|
+
import "@photo-sphere-viewer/gallery-plugin/index.css";
|
|
13
|
+
import "@photo-sphere-viewer/markers-plugin/index.css";
|
|
14
|
+
import { Viewer as PSViewer } from "@photo-sphere-viewer/core";
|
|
15
|
+
import { VirtualTourPlugin } from "@photo-sphere-viewer/virtual-tour-plugin";
|
|
16
|
+
import PhotoAdapter from "../utils/PhotoAdapter";
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
// Default panorama (logo)
|
|
20
|
+
const BASE_PANORAMA = {
|
|
21
|
+
baseUrl: LoaderImgBase,
|
|
22
|
+
width: 1280,
|
|
23
|
+
cols: 2,
|
|
24
|
+
rows: 1,
|
|
25
|
+
tileUrl: () => null,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const PSV_DEFAULT_ZOOM = 30;
|
|
29
|
+
export const PSV_ANIM_DURATION = 250;
|
|
30
|
+
export const PIC_MAX_STAY_DURATION = 3000;
|
|
31
|
+
|
|
32
|
+
PSViewer.useNewAnglesOrder = true;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Photo is the component showing a single picture.
|
|
36
|
+
* It uses Photo Sphere Viewer as a basis, and pre-configure dialog with STAC API.
|
|
37
|
+
*
|
|
38
|
+
* Note that all functions of [PhotoSphereViewer Viewer class](https://photo-sphere-viewer.js.org/api/classes/core.viewer) are available as well.
|
|
39
|
+
*
|
|
40
|
+
* @param {CoreView} parent The parent view
|
|
41
|
+
* @param {Element} container The DOM element to create into
|
|
42
|
+
* @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)
|
|
43
|
+
* @param {number} [options.transitionDuration] The number of milliseconds the transition animation should be.
|
|
44
|
+
* @param {function} [options.shouldGoFast] Function returning a boolean to indicate if we may skip loading HD images.
|
|
45
|
+
*/
|
|
46
|
+
export default class Photo extends PSViewer {
|
|
47
|
+
constructor(parent, container, options = {}) {
|
|
48
|
+
super({
|
|
49
|
+
container,
|
|
50
|
+
adapter: [PhotoAdapter, {
|
|
51
|
+
showErrorTile: false,
|
|
52
|
+
baseBlur: false,
|
|
53
|
+
resolution: parent.isWidthSmall() ? 32 : 64,
|
|
54
|
+
shouldGoFast: options.shouldGoFast,
|
|
55
|
+
}],
|
|
56
|
+
withCredentials: parent._options?.fetchOptions?.credentials == "include",
|
|
57
|
+
requestHeaders: parent._options?.fetchOptions?.headers,
|
|
58
|
+
panorama: BASE_PANORAMA,
|
|
59
|
+
lang: parent._t.psv,
|
|
60
|
+
minFov: 5,
|
|
61
|
+
loadingTxt: " ",
|
|
62
|
+
navbar: null,
|
|
63
|
+
rendererParameters: {
|
|
64
|
+
preserveDrawingBuffer: !parent.isWidthSmall(),
|
|
65
|
+
},
|
|
66
|
+
plugins: [
|
|
67
|
+
[VirtualTourPlugin, {
|
|
68
|
+
dataMode: "server",
|
|
69
|
+
positionMode: "gps",
|
|
70
|
+
renderMode: "3d",
|
|
71
|
+
preload: true,
|
|
72
|
+
getNode: () => {},
|
|
73
|
+
transitionOptions: () => {},
|
|
74
|
+
arrowsPosition: {
|
|
75
|
+
linkOverlapAngle: Math.PI / 6,
|
|
76
|
+
}
|
|
77
|
+
}],
|
|
78
|
+
],
|
|
79
|
+
...options
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this._parent = parent;
|
|
83
|
+
container.classList.add("gvs-psv");
|
|
84
|
+
this._shouldGoFast = options?.shouldGoFast || (() => false);
|
|
85
|
+
this._transitionDuration = options?.transitionDuration || PSV_ANIM_DURATION;
|
|
86
|
+
this._myVTour = this.getPlugin(VirtualTourPlugin);
|
|
87
|
+
this._myVTour.datasource.nodeResolver = this._getNodeFromAPI.bind(this);
|
|
88
|
+
this._myVTour.config.transitionOptions = this._psvNodeTransition.bind(this);
|
|
89
|
+
this._clearArrows = this._myVTour.arrowsRenderer.clear.bind(this._myVTour.arrowsRenderer);
|
|
90
|
+
this._myVTour.arrowsRenderer.clear = () => {};
|
|
91
|
+
|
|
92
|
+
// Cache to find sequence ID for a single picture
|
|
93
|
+
this._picturesSequences = {};
|
|
94
|
+
|
|
95
|
+
// Offer various custom events
|
|
96
|
+
this._myVTour.addEventListener("enter-arrow", this._onEnterArrow.bind(this));
|
|
97
|
+
this._myVTour.addEventListener("leave-arrow", this._onLeaveArrow.bind(this));
|
|
98
|
+
this._myVTour.addEventListener("node-changed", this._onNodeChanged.bind(this));
|
|
99
|
+
this.addEventListener("position-updated", this._onPositionUpdated.bind(this));
|
|
100
|
+
this.addEventListener("zoom-updated", this._onZoomUpdated.bind(this));
|
|
101
|
+
this._parent.addEventListener("select", this._onSelect.bind(this));
|
|
102
|
+
|
|
103
|
+
// Fix for loader circle background not showing up
|
|
104
|
+
this.loader.size = 150;
|
|
105
|
+
this.loader.color = "rgba(61, 61, 61, 0.5)";
|
|
106
|
+
this.loader.textColor = "rgba(255, 255, 255, 0.7)";
|
|
107
|
+
this.loader.border = 5;
|
|
108
|
+
this.loader.thickness = 10;
|
|
109
|
+
this.loader.canvas.setAttribute("viewBox", "0 0 150 150");
|
|
110
|
+
this.loader.__updateContent();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Calls API to retrieve a certain picture, then transforms into PSV format
|
|
115
|
+
*
|
|
116
|
+
* @private
|
|
117
|
+
* @param {string} picId The picture UUID
|
|
118
|
+
* @returns {Promise} Resolves on PSV node metadata
|
|
119
|
+
*/
|
|
120
|
+
async _getNodeFromAPI(picId) {
|
|
121
|
+
if(!picId) { return; }
|
|
122
|
+
|
|
123
|
+
const picApiResponse = await fetch(
|
|
124
|
+
this._parent._api.getPictureMetadataUrl(picId, this._picturesSequences[picId]),
|
|
125
|
+
this._parent._api._getFetchOptions()
|
|
126
|
+
);
|
|
127
|
+
let metadata = await picApiResponse.json();
|
|
128
|
+
|
|
129
|
+
if(metadata.features) { metadata = metadata.features.pop(); }
|
|
130
|
+
if(!metadata || Object.keys(metadata).length === 0 || !picApiResponse.ok) {
|
|
131
|
+
if(this._parent._loader) {
|
|
132
|
+
this._parent._loader.dismiss(true, this._parent._t.gvs.error_pic);
|
|
133
|
+
}
|
|
134
|
+
throw new Error("Picture with ID " + picId + " was not found");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this._picturesSequences[picId] = metadata.collection;
|
|
138
|
+
const node = apiFeatureToPSVNode(
|
|
139
|
+
metadata,
|
|
140
|
+
this._parent._t,
|
|
141
|
+
this._parent._isInternetFast,
|
|
142
|
+
this._parent._picturesNavFilter?.bind(this._parent)
|
|
143
|
+
);
|
|
144
|
+
if(node?.sequence?.prevPic) { this._picturesSequences[node?.sequence?.prevPic] = metadata.collection; }
|
|
145
|
+
if(node?.sequence?.nextPic) { this._picturesSequences[node?.sequence?.nextPic] = metadata.collection; }
|
|
146
|
+
|
|
147
|
+
return node;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* PSV node transition handler
|
|
152
|
+
* @param {*} toNode Next loading node
|
|
153
|
+
* @param {*} [fromNode] Currently shown node (previous)
|
|
154
|
+
* @param {*} [fromLink] Link clicked by user to go from current to next node
|
|
155
|
+
* @private
|
|
156
|
+
*/
|
|
157
|
+
_psvNodeTransition(toNode, fromNode, fromLink) {
|
|
158
|
+
let nodeTransition = {};
|
|
159
|
+
|
|
160
|
+
const animationDuration = this._shouldGoFast() ? 0 : Math.min(PSV_ANIM_DURATION, this._transitionDuration);
|
|
161
|
+
const animated = animationDuration > 100;
|
|
162
|
+
const following = (fromLink || fromNode?.links.find(a => a.nodeId == toNode.id)) != null;
|
|
163
|
+
const sameSequence = fromNode && toNode.sequence.id === fromNode.sequence.id;
|
|
164
|
+
const fromNodeHeading = (fromNode?.properties?.["view:azimuth"] || 0) * (Math.PI / 180);
|
|
165
|
+
const toNodeHeading = (toNode?.properties?.["view:azimuth"] || 0) * (Math.PI / 180);
|
|
166
|
+
|
|
167
|
+
this.setOption("maxFov", Math.min(toNode.horizontalFov * 3/4, 90));
|
|
168
|
+
|
|
169
|
+
const centerNoAnim = {
|
|
170
|
+
speed: 0,
|
|
171
|
+
fadeIn: false,
|
|
172
|
+
rotation: false,
|
|
173
|
+
rotateTo: { pitch: 0, yaw: 0 },
|
|
174
|
+
zoomTo: PSV_DEFAULT_ZOOM
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Going to 360
|
|
178
|
+
if(toNode.horizontalFov == 360) {
|
|
179
|
+
// No previous sequence -> Point to center + no animation
|
|
180
|
+
if(!fromNode) {
|
|
181
|
+
nodeTransition = centerNoAnim;
|
|
182
|
+
}
|
|
183
|
+
// Has a previous sequence
|
|
184
|
+
else {
|
|
185
|
+
// Far away sequences -> Point to center + no animation
|
|
186
|
+
if(getDistance(fromNode.gps, toNode.gps) >= 0.001) {
|
|
187
|
+
nodeTransition = centerNoAnim;
|
|
188
|
+
}
|
|
189
|
+
// Nearby sequences -> Keep orientation
|
|
190
|
+
else {
|
|
191
|
+
nodeTransition = {
|
|
192
|
+
speed: animationDuration,
|
|
193
|
+
fadeIn: following && animated,
|
|
194
|
+
rotation: following && sameSequence && animated,
|
|
195
|
+
rotateTo: this.getPosition()
|
|
196
|
+
};
|
|
197
|
+
nodeTransition.rotateTo.yaw += fromNodeHeading - toNodeHeading;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Going to flat
|
|
202
|
+
else {
|
|
203
|
+
// Same sequence -> Point to center + animation if following pics + not vomiting
|
|
204
|
+
if(sameSequence) {
|
|
205
|
+
const fromYaw = this.getPosition().yaw;
|
|
206
|
+
const fovMaxYaw = (fromNode.horizontalFov * (Math.PI / 180)) / 2;
|
|
207
|
+
const keepZoomPos = fromYaw <= fovMaxYaw || fromYaw >= (2 * Math.PI - fovMaxYaw);
|
|
208
|
+
const notTooMuchRotation = Math.abs(fromNodeHeading - toNodeHeading) <= Math.PI / 4;
|
|
209
|
+
nodeTransition = {
|
|
210
|
+
speed: animationDuration,
|
|
211
|
+
fadeIn: following && notTooMuchRotation && animated,
|
|
212
|
+
rotation: following && notTooMuchRotation && animated,
|
|
213
|
+
rotateTo: keepZoomPos ? this.getPosition() : { pitch: 0, yaw: 0 },
|
|
214
|
+
zoomTo: keepZoomPos ? this.getZoomLevel() : PSV_DEFAULT_ZOOM,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// Different sequence -> Point to center + no animation
|
|
218
|
+
else {
|
|
219
|
+
nodeTransition = centerNoAnim;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if(nodeTransition.fadeIn && nodeTransition.speed >= 150) {
|
|
224
|
+
setTimeout(this._clearArrows, nodeTransition.speed-100);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
this._clearArrows();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Event for picture starting to load
|
|
233
|
+
*
|
|
234
|
+
* @event psv:picture-loading
|
|
235
|
+
* @memberof CoreView
|
|
236
|
+
* @type {object}
|
|
237
|
+
* @property {object} detail Event information
|
|
238
|
+
* @property {string} detail.picId The picture unique identifier
|
|
239
|
+
* @property {number} detail.lon Longitude (WGS84)
|
|
240
|
+
* @property {number} detail.lat Latitude (WGS84)
|
|
241
|
+
* @property {number} detail.x New x position (in degrees, 0-360), corresponds to heading (0° = North, 90° = East, 180° = South, 270° = West)
|
|
242
|
+
* @property {number} detail.y New y position (in degrees)
|
|
243
|
+
* @property {number} detail.z New z position (0-100)
|
|
244
|
+
*/
|
|
245
|
+
const event = new CustomEvent("psv:picture-loading", {
|
|
246
|
+
detail: {
|
|
247
|
+
...Object.assign({},
|
|
248
|
+
this.getXYZ(),
|
|
249
|
+
nodeTransition.rotateTo ? { x: (toNodeHeading + nodeTransition.rotateTo.yaw) * 180 / Math.PI } : null,
|
|
250
|
+
nodeTransition.zoomTo ? { z: nodeTransition.zoomTo } : null
|
|
251
|
+
),
|
|
252
|
+
picId: toNode.id,
|
|
253
|
+
lon: toNode.gps[0],
|
|
254
|
+
lat: toNode.gps[1]
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
this._parent.dispatchEvent(event);
|
|
258
|
+
|
|
259
|
+
return nodeTransition;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Event handler for PSV arrow hover.
|
|
264
|
+
* It creates a custom event "picture-preview-started"
|
|
265
|
+
* @private
|
|
266
|
+
* @param {object} e The event data
|
|
267
|
+
*/
|
|
268
|
+
_onEnterArrow(e) {
|
|
269
|
+
const fromLink = e.link;
|
|
270
|
+
const fromNode = e.node;
|
|
271
|
+
|
|
272
|
+
// Find probable direction for previewed picture
|
|
273
|
+
let direction;
|
|
274
|
+
if(fromNode) {
|
|
275
|
+
if(fromNode.horizontalFov === 360) {
|
|
276
|
+
direction = (this.getPictureOriginalHeading() + this.getPosition().yaw * 180 / Math.PI) % 360;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
direction = this.getPictureOriginalHeading();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Event for picture preview
|
|
285
|
+
*
|
|
286
|
+
* @event psv:picture-preview-started
|
|
287
|
+
* @memberof CoreView
|
|
288
|
+
* @type {object}
|
|
289
|
+
* @property {object} detail Event information
|
|
290
|
+
* @property {string} detail.picId The picture ID
|
|
291
|
+
* @property {number[]} detail.coordinates [x,y] coordinates
|
|
292
|
+
* @property {number} detail.direction The theorical picture orientation
|
|
293
|
+
*/
|
|
294
|
+
const event = new CustomEvent("psv:picture-preview-started", { detail: {
|
|
295
|
+
picId: fromLink.nodeId,
|
|
296
|
+
coordinates: fromLink.gps,
|
|
297
|
+
direction,
|
|
298
|
+
}});
|
|
299
|
+
this._parent.dispatchEvent(event);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Event handler for PSV arrow end of hovering.
|
|
304
|
+
* It creates a custom event "picture-preview-stopped"
|
|
305
|
+
* @private
|
|
306
|
+
* @param {object} e The event data
|
|
307
|
+
*/
|
|
308
|
+
_onLeaveArrow(e) {
|
|
309
|
+
const fromLink = e.link;
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Event for end of picture preview
|
|
313
|
+
*
|
|
314
|
+
* @event psv:picture-preview-stopped
|
|
315
|
+
* @memberof CoreView
|
|
316
|
+
* @type {object}
|
|
317
|
+
* @property {object} detail Event information
|
|
318
|
+
* @property {string} detail.picId The picture ID
|
|
319
|
+
*/
|
|
320
|
+
const event = new CustomEvent("psv:picture-preview-stopped", { detail: {
|
|
321
|
+
picId: fromLink.nodeId,
|
|
322
|
+
}});
|
|
323
|
+
this._parent.dispatchEvent(event);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Event handler for position update in PSV.
|
|
328
|
+
* Allows to send a custom "view-rotated" event.
|
|
329
|
+
* @private
|
|
330
|
+
*/
|
|
331
|
+
_onPositionUpdated({position}) {
|
|
332
|
+
const pos = positionToXYZ(position, this.getZoomLevel());
|
|
333
|
+
pos.x += this.getPictureOriginalHeading();
|
|
334
|
+
pos.x = pos.x % 360;
|
|
335
|
+
/**
|
|
336
|
+
* Event for viewer rotation
|
|
337
|
+
*
|
|
338
|
+
* @event psv:view-rotated
|
|
339
|
+
* @memberof CoreView
|
|
340
|
+
* @type {object}
|
|
341
|
+
* @property {object} detail Event information
|
|
342
|
+
* @property {number} detail.x New x position (in degrees, 0-360), corresponds to heading (0° = North, 90° = East, 180° = South, 270° = West)
|
|
343
|
+
* @property {number} detail.y New y position (in degrees)
|
|
344
|
+
* @property {number} detail.z New Z position (between 0 and 100)
|
|
345
|
+
*/
|
|
346
|
+
const event = new CustomEvent("psv:view-rotated", { detail: pos });
|
|
347
|
+
this._parent.dispatchEvent(event);
|
|
348
|
+
|
|
349
|
+
this._onTilesStartLoading();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Event handler for zoom updates in PSV.
|
|
354
|
+
* Allows to send a custom "view-rotated" event.
|
|
355
|
+
* @private
|
|
356
|
+
*/
|
|
357
|
+
_onZoomUpdated({zoomLevel}) {
|
|
358
|
+
const event = new CustomEvent("psv:view-rotated", { detail: { ...this.getXY(), z: zoomLevel} });
|
|
359
|
+
this._parent.dispatchEvent(event);
|
|
360
|
+
|
|
361
|
+
this._onTilesStartLoading();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Event handler for node change in PSV.
|
|
366
|
+
* Allows to send a custom "picture-loaded" event.
|
|
367
|
+
* @private
|
|
368
|
+
*/
|
|
369
|
+
_onNodeChanged(e) {
|
|
370
|
+
// Clean up clicked arrows
|
|
371
|
+
for(let d of document.getElementsByClassName("gvs-psv-tour-arrows")) {
|
|
372
|
+
d.classList.remove("gvs-clicked");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if(e.node.id) {
|
|
376
|
+
this._parent.select(e.node?.sequence?.id, e.node.id);
|
|
377
|
+
const picMeta = this.getPictureMetadata();
|
|
378
|
+
if(!picMeta) { return; }
|
|
379
|
+
this._prevSequence = picMeta.sequence.id;
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Event for picture load (low-resolution image is loaded)
|
|
383
|
+
*
|
|
384
|
+
* @event psv:picture-loaded
|
|
385
|
+
* @memberof CoreView
|
|
386
|
+
* @type {object}
|
|
387
|
+
* @property {object} detail Event information
|
|
388
|
+
* @property {string} detail.picId The picture unique identifier
|
|
389
|
+
* @property {number} detail.lon Longitude (WGS84)
|
|
390
|
+
* @property {number} detail.lat Latitude (WGS84)
|
|
391
|
+
* @property {number} detail.x New x position (in degrees, 0-360), corresponds to heading (0° = North, 90° = East, 180° = South, 270° = West)
|
|
392
|
+
* @property {number} detail.y New y position (in degrees)
|
|
393
|
+
* @property {number} detail.z New z position (0-100)
|
|
394
|
+
*/
|
|
395
|
+
const event = new CustomEvent("psv:picture-loaded", {
|
|
396
|
+
detail: {
|
|
397
|
+
...this.getXYZ(),
|
|
398
|
+
picId: e.node.id,
|
|
399
|
+
lon: picMeta.gps[0],
|
|
400
|
+
lat: picMeta.gps[1]
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
this._parent.dispatchEvent(event);
|
|
404
|
+
|
|
405
|
+
// Change download URL
|
|
406
|
+
if(picMeta.panorama.hdUrl) {
|
|
407
|
+
this.setOption("downloadUrl", picMeta.panorama.hdUrl);
|
|
408
|
+
this.setOption("downloadName", e.node.id+".jpg");
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
this.setOption("downloadUrl", null);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
this._onTilesStartLoading();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Event handler for loading a new range of tiles
|
|
420
|
+
*
|
|
421
|
+
* @private
|
|
422
|
+
*/
|
|
423
|
+
_onTilesStartLoading() {
|
|
424
|
+
if(this._tilesQueueTimer) {
|
|
425
|
+
clearInterval(this._tilesQueueTimer);
|
|
426
|
+
delete this._tilesQueueTimer;
|
|
427
|
+
}
|
|
428
|
+
this._tilesQueueTimer = setInterval(() => {
|
|
429
|
+
if(Object.keys(this.adapter.queue.tasks).length === 0) {
|
|
430
|
+
if(this._myVTour.state.currentNode) {
|
|
431
|
+
/**
|
|
432
|
+
* Event launched when all visible tiles of a picture are loaded
|
|
433
|
+
*
|
|
434
|
+
* @event psv:picture-tiles-loaded
|
|
435
|
+
* @memberof CoreView
|
|
436
|
+
* @type {object}
|
|
437
|
+
* @property {object} detail Event information
|
|
438
|
+
* @property {string} detail.picId The picture unique identifier
|
|
439
|
+
*/
|
|
440
|
+
const event = new Event("psv:picture-tiles-loaded", { picId: this._myVTour.state.currentNode.id });
|
|
441
|
+
this._parent.dispatchEvent(event);
|
|
442
|
+
}
|
|
443
|
+
clearInterval(this._tilesQueueTimer);
|
|
444
|
+
delete this._tilesQueueTimer;
|
|
445
|
+
}
|
|
446
|
+
}, 100);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Access currently shown picture metadata
|
|
451
|
+
*
|
|
452
|
+
* @returns {object} Picture metadata
|
|
453
|
+
*/
|
|
454
|
+
getPictureMetadata() {
|
|
455
|
+
return this._myVTour.state.currentNode ? Object.assign({}, this._myVTour.state.currentNode) : null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Handler for select event.
|
|
460
|
+
* @private
|
|
461
|
+
*/
|
|
462
|
+
_onSelect(e) {
|
|
463
|
+
if(e.detail.seqId) {
|
|
464
|
+
this._picturesSequences[e.detail.picId] = e.detail.seqId;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if(this._myVTour.getCurrentNode()?.id !== e.detail.picId) {
|
|
468
|
+
this.loader.show();
|
|
469
|
+
this._myVTour.setCurrentNode(e.detail.picId).catch(e => {
|
|
470
|
+
this.showErrorOverlay(e, this._parent._t.gvs.error_pic, true);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Displays next picture in current sequence (if any)
|
|
477
|
+
*/
|
|
478
|
+
goToNextPicture() {
|
|
479
|
+
if(!this.getPictureMetadata()) {
|
|
480
|
+
throw new Error("No picture currently selected");
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const next = this.getPictureMetadata().sequence.nextPic;
|
|
484
|
+
if(next) {
|
|
485
|
+
this._parent.select(this.getPictureMetadata().sequence.id, next);
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
throw new Error("No next picture available");
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Displays previous picture in current sequence (if any)
|
|
494
|
+
*/
|
|
495
|
+
goToPrevPicture() {
|
|
496
|
+
if(!this.getPictureMetadata()) {
|
|
497
|
+
throw new Error("No picture currently selected");
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const prev = this.getPictureMetadata().sequence.prevPic;
|
|
501
|
+
if(prev) {
|
|
502
|
+
this._parent.select(this.getPictureMetadata().sequence.id, prev);
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
throw new Error("No previous picture available");
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Displays in viewer a picture near to given coordinates
|
|
511
|
+
*
|
|
512
|
+
* @param {number} lat Latitude (WGS84)
|
|
513
|
+
* @param {number} lon Longitude (WGS84)
|
|
514
|
+
* @returns {Promise} Resolves on picture ID if picture found, otherwise rejects
|
|
515
|
+
*/
|
|
516
|
+
async goToPosition(lat, lon) {
|
|
517
|
+
return this._parent._api.getPicturesAroundCoordinates(lat, lon)
|
|
518
|
+
.then(res => {
|
|
519
|
+
if(res.features.length > 0) {
|
|
520
|
+
const f = res.features.pop();
|
|
521
|
+
this._parent.select(
|
|
522
|
+
f?.collection,
|
|
523
|
+
f.id
|
|
524
|
+
);
|
|
525
|
+
return f.id;
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
return Promise.reject(new Error("No picture found nearby given coordinates"));
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Get 2D position of sphere currently shown to user
|
|
535
|
+
*
|
|
536
|
+
* @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) }
|
|
537
|
+
*/
|
|
538
|
+
getXY() {
|
|
539
|
+
const pos = positionToXYZ(this.getPosition());
|
|
540
|
+
pos.x = (pos.x + this.getPictureOriginalHeading()) % 360;
|
|
541
|
+
return pos;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Get 3D position of sphere currently shown to user
|
|
546
|
+
*
|
|
547
|
+
* @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) }
|
|
548
|
+
*/
|
|
549
|
+
getXYZ() {
|
|
550
|
+
const pos = this.getXY();
|
|
551
|
+
pos.z = this.getZoomLevel();
|
|
552
|
+
return pos;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Get capture orientation of current picture, based on its GPS.
|
|
557
|
+
* @returns Picture original heading in degrees (0 to 360°)
|
|
558
|
+
*/
|
|
559
|
+
getPictureOriginalHeading() {
|
|
560
|
+
return this.getPictureMetadata()?.properties?.["view:azimuth"] || 0;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Computes the relative heading of currently selected picture.
|
|
565
|
+
* This gives the angle of capture compared to sequence path (vehicle movement).
|
|
566
|
+
*
|
|
567
|
+
* @returns Relative heading in degrees (-180 to 180)
|
|
568
|
+
*/
|
|
569
|
+
getPictureRelativeHeading() {
|
|
570
|
+
return getRelativeHeading(this.getPictureMetadata());
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Clears the Photo Sphere Viewer metadata cache.
|
|
575
|
+
* It is useful when current picture or sequence has changed server-side after first load.
|
|
576
|
+
*/
|
|
577
|
+
clearPictureMetadataCache() {
|
|
578
|
+
const oldPicId = this.getPictureMetadata()?.id;
|
|
579
|
+
const oldSeqId = this.getPictureMetadata()?.sequence?.id;
|
|
580
|
+
|
|
581
|
+
// Force deletion of cached metadata in PSV
|
|
582
|
+
this._myVTour.state.currentTooltip?.hide();
|
|
583
|
+
this._myVTour.state.currentTooltip = null;
|
|
584
|
+
this._myVTour.state.currentNode = null;
|
|
585
|
+
this._myVTour.state.preload = {};
|
|
586
|
+
this._myVTour.datasource.nodes = {};
|
|
587
|
+
|
|
588
|
+
// Reload current picture if one was selected
|
|
589
|
+
if(oldPicId) {
|
|
590
|
+
this._parent.select(oldSeqId, oldPicId);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Change the shown position in picture
|
|
596
|
+
*
|
|
597
|
+
* @param {number} x X position (in degrees)
|
|
598
|
+
* @param {number} y Y position (in degrees)
|
|
599
|
+
* @param {number} z Z position (0-100)
|
|
600
|
+
*/
|
|
601
|
+
setXYZ(x, y, z) {
|
|
602
|
+
const coords = xyzToPosition(x - this.getPictureOriginalHeading(), y, z);
|
|
603
|
+
this.rotate({ yaw: coords.yaw, pitch: coords.pitch });
|
|
604
|
+
this.zoom(coords.zoom);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Enable or disable higher contrast on picture
|
|
609
|
+
* @param {boolean} enable True to enable higher contrast
|
|
610
|
+
*/
|
|
611
|
+
setHigherContrast(enable) {
|
|
612
|
+
this.renderer.renderer.toneMapping = enable ? 3 : 0;
|
|
613
|
+
this.renderer.renderer.toneMappingExposure = enable ? 2 : 1;
|
|
614
|
+
this.needsUpdate();
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Get the duration of stay on a picture during a sequence play.
|
|
619
|
+
* @returns {number} The duration (in milliseconds)
|
|
620
|
+
*/
|
|
621
|
+
getTransitionDuration() {
|
|
622
|
+
return this._transitionDuration;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Changes the duration of stay on a picture during a sequence play.
|
|
627
|
+
*
|
|
628
|
+
* @param {number} value The new duration (in milliseconds, between 100 and 3000)
|
|
629
|
+
*/
|
|
630
|
+
setTransitionDuration(value) {
|
|
631
|
+
value = parseFloat(value);
|
|
632
|
+
if(value < 100 || value > PIC_MAX_STAY_DURATION) {
|
|
633
|
+
throw new Error("Invalid transition duration (should be between 100 and "+PIC_MAX_STAY_DURATION+")");
|
|
634
|
+
}
|
|
635
|
+
this._transitionDuration = value;
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Event for transition duration change
|
|
639
|
+
*
|
|
640
|
+
* @event psv:transition-duration-changed
|
|
641
|
+
* @memberof CoreView
|
|
642
|
+
* @type {object}
|
|
643
|
+
* @property {object} detail Event information
|
|
644
|
+
* @property {string} detail.duration New duration (in milliseconds)
|
|
645
|
+
*/
|
|
646
|
+
const event = new CustomEvent("psv:transition-duration-changed", { detail: { value } });
|
|
647
|
+
this._parent.dispatchEvent(event);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
setPanorama(path, options) {
|
|
651
|
+
const onFailure = e => this.showErrorOverlay(e, this._parent._t.gvs.error_pic, true);
|
|
652
|
+
try {
|
|
653
|
+
return super.setPanorama(path, options).catch(onFailure);
|
|
654
|
+
}
|
|
655
|
+
catch(e) {
|
|
656
|
+
onFailure(e);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Display an error message to user on screen
|
|
662
|
+
* @param {object} e The initial error
|
|
663
|
+
* @param {str} label The main error label to display
|
|
664
|
+
* @param {boolean} dissmisable Is error dissmisable
|
|
665
|
+
*/
|
|
666
|
+
showErrorOverlay(e, label, dissmisable) {
|
|
667
|
+
if(this._parent._loader.isVisible() || !this.overlay.isVisible()) {
|
|
668
|
+
this._parent._loader.dismiss(
|
|
669
|
+
e,
|
|
670
|
+
label,
|
|
671
|
+
dissmisable ? () => {
|
|
672
|
+
this._parent._loader.dismiss();
|
|
673
|
+
this.overlay.hide();
|
|
674
|
+
} : undefined
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
console.error(e);
|
|
679
|
+
this.overlay.show({
|
|
680
|
+
image: `<img style="width: 200px" src="${LogoDead}" />`,
|
|
681
|
+
title: this._parent._t.gvs.error,
|
|
682
|
+
text: label + "<br />" + this._parent._t.gvs.error_click,
|
|
683
|
+
dissmisable,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<svg
|
|
3
|
+
width="48"
|
|
4
|
+
height="48"
|
|
5
|
+
viewBox="0 0 48 48"
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
8
|
+
<path
|
|
9
|
+
style="fill:#ffcc80;fill-opacity:1;stroke:#ffffff;stroke-width:2.63736;stroke-dasharray:none;stroke-opacity:1"
|
|
10
|
+
d="M 24 1.3183594 A 22.68132 22.68132 0 0 0 1.3183594 24 A 22.68132 22.68132 0 0 0 24 46.681641 A 22.68132 22.68132 0 0 0 46.681641 24 A 22.68132 22.68132 0 0 0 24 1.3183594 z M 24 14.021484 A 9.9777565 9.9777565 0 0 1 33.978516 24 A 9.9777565 9.9777565 0 0 1 24 33.978516 A 9.9777565 9.9777565 0 0 1 14.021484 24 A 9.9777565 9.9777565 0 0 1 24 14.021484 z " />
|
|
11
|
+
<path
|
|
12
|
+
style="fill:#bf360c;fill-opacity:1;stroke:#ffffff;stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
13
|
+
d="M 24 1.2421875 A 22.594107 22.594107 0 0 0 8.0214844 7.859375 L 17.042969 16.882812 A 9.9777565 9.9777565 0 0 1 24 14.021484 A 9.9777565 9.9777565 0 0 1 30.974609 16.867188 L 39.978516 7.8632812 A 22.594107 22.594107 0 0 0 24 1.2421875 z " />
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<svg
|
|
3
|
+
width="48"
|
|
4
|
+
height="48"
|
|
5
|
+
viewBox="0 0 48 48"
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
8
|
+
<path
|
|
9
|
+
style="fill:#bf360c;fill-opacity:1;stroke:#ffffff;stroke-width:2.50003;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
10
|
+
d="M 24 1.2421875 A 22.594107 22.594107 0 0 0 8.0214844 7.859375 L 17.042969 16.882812 A 9.9777565 9.9777565 0 0 1 24 14.021484 A 9.9777565 9.9777565 0 0 1 30.974609 16.867188 L 39.978516 7.8632812 A 22.594107 22.594107 0 0 0 24 1.2421875 z " />
|
|
11
|
+
</svg>
|