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