@panoramax/web-viewer 3.2.3-develop-d7e5a16d → 3.2.3-develop-6257391e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/.gitlab-ci.yml +3 -0
  2. package/CHANGELOG.md +19 -0
  3. package/CODE_OF_CONDUCT.md +1 -1
  4. package/README.md +1 -1
  5. package/build/editor.html +10 -1
  6. package/build/index.css +2 -2
  7. package/build/index.css.map +1 -1
  8. package/build/index.html +1 -1
  9. package/build/index.js +1682 -5
  10. package/build/index.js.map +1 -1
  11. package/build/map.html +1 -1
  12. package/build/viewer.html +10 -1
  13. package/build/widgets.html +1 -0
  14. package/config/jest/mocks.js +172 -0
  15. package/config/paths.js +1 -0
  16. package/config/webpack.config.js +26 -0
  17. package/docs/03_URL_settings.md +3 -11
  18. package/docs/05_Compatibility.md +59 -76
  19. package/docs/09_Develop.md +30 -11
  20. package/docs/90_Releases.md +2 -2
  21. package/docs/images/class_diagram.drawio +28 -28
  22. package/docs/images/class_diagram.jpg +0 -0
  23. package/docs/index.md +112 -0
  24. package/docs/reference/components/core/Basic.md +153 -0
  25. package/docs/reference/components/core/CoverageMap.md +160 -0
  26. package/docs/reference/components/core/Editor.md +172 -0
  27. package/docs/reference/components/core/Viewer.md +288 -0
  28. package/docs/reference/components/layout/CorneredGrid.md +29 -0
  29. package/docs/reference/components/layout/Mini.md +45 -0
  30. package/docs/reference/components/menus/MapBackground.md +32 -0
  31. package/docs/reference/components/menus/MapFilters.md +15 -0
  32. package/docs/reference/components/menus/MapLayers.md +15 -0
  33. package/docs/reference/components/menus/MapLegend.md +15 -0
  34. package/docs/reference/components/menus/PictureLegend.md +15 -0
  35. package/docs/reference/components/menus/PictureMetadata.md +15 -0
  36. package/docs/reference/components/menus/PlayerOptions.md +15 -0
  37. package/docs/reference/components/menus/QualityScoreDoc.md +15 -0
  38. package/docs/reference/components/menus/ReportForm.md +15 -0
  39. package/docs/reference/components/menus/ShareMenu.md +15 -0
  40. package/docs/reference/components/ui/Button.md +39 -0
  41. package/docs/reference/components/ui/ButtonGroup.md +36 -0
  42. package/docs/reference/components/ui/CopyButton.md +35 -0
  43. package/docs/reference/components/ui/Grade.md +32 -0
  44. package/docs/reference/components/ui/LinkButton.md +44 -0
  45. package/docs/reference/components/ui/Loader.md +54 -0
  46. package/docs/reference/components/ui/Map.md +214 -0
  47. package/docs/reference/components/ui/MapMore.md +233 -0
  48. package/docs/reference/components/ui/Photo.md +369 -0
  49. package/docs/reference/components/ui/Popup.md +56 -0
  50. package/docs/reference/components/ui/QualityScore.md +45 -0
  51. package/docs/reference/components/ui/SearchBar.md +63 -0
  52. package/docs/reference/components/ui/TogglableGroup.md +39 -0
  53. package/docs/reference/components/ui/widgets/GeoSearch.md +32 -0
  54. package/docs/reference/components/ui/widgets/Legend.md +32 -0
  55. package/docs/reference/components/ui/widgets/MapFiltersButton.md +33 -0
  56. package/docs/reference/components/ui/widgets/MapLayersButton.md +15 -0
  57. package/docs/reference/components/ui/widgets/Player.md +32 -0
  58. package/docs/reference/components/ui/widgets/Share.md +15 -0
  59. package/docs/reference/components/ui/widgets/Zoom.md +15 -0
  60. package/docs/reference/utils/API.md +311 -0
  61. package/docs/reference/utils/InitParameters.md +67 -0
  62. package/docs/reference/utils/URLHandler.md +102 -0
  63. package/docs/reference.md +73 -0
  64. package/docs/shortcuts.md +11 -0
  65. package/docs/tutorials/aerial_imagery.md +19 -0
  66. package/docs/tutorials/authentication.md +10 -0
  67. package/docs/tutorials/custom_widgets.md +64 -0
  68. package/docs/tutorials/map_style.md +27 -0
  69. package/docs/tutorials/migrate_v4.md +122 -0
  70. package/docs/tutorials/synced_coverage.md +42 -0
  71. package/mkdocs.yml +60 -5
  72. package/package.json +10 -7
  73. package/public/editor.html +21 -29
  74. package/public/index.html +3 -3
  75. package/public/map.html +19 -18
  76. package/public/viewer.html +18 -24
  77. package/public/widgets.html +265 -0
  78. package/scripts/doc.js +77 -0
  79. package/src/components/core/Basic.css +44 -0
  80. package/src/components/core/Basic.js +258 -0
  81. package/src/components/core/CoverageMap.css +9 -0
  82. package/src/components/core/CoverageMap.js +105 -0
  83. package/src/components/core/Editor.css +23 -0
  84. package/src/components/core/Editor.js +354 -0
  85. package/src/components/core/Viewer.css +109 -0
  86. package/src/components/core/Viewer.js +707 -0
  87. package/src/components/core/index.js +11 -0
  88. package/src/components/index.js +13 -0
  89. package/src/components/layout/CorneredGrid.js +109 -0
  90. package/src/components/layout/Mini.js +117 -0
  91. package/src/components/layout/index.js +7 -0
  92. package/src/components/menus/MapBackground.js +106 -0
  93. package/src/components/menus/MapFilters.js +386 -0
  94. package/src/components/menus/MapLayers.js +143 -0
  95. package/src/components/menus/MapLegend.js +54 -0
  96. package/src/components/menus/PictureLegend.js +103 -0
  97. package/src/components/menus/PictureMetadata.js +188 -0
  98. package/src/components/menus/PlayerOptions.js +96 -0
  99. package/src/components/menus/QualityScoreDoc.js +36 -0
  100. package/src/components/menus/ReportForm.js +133 -0
  101. package/src/components/menus/Share.js +228 -0
  102. package/src/components/menus/index.js +15 -0
  103. package/src/components/styles.js +365 -0
  104. package/src/components/ui/Button.js +75 -0
  105. package/src/components/ui/ButtonGroup.css +49 -0
  106. package/src/components/ui/ButtonGroup.js +68 -0
  107. package/src/components/ui/CopyButton.js +71 -0
  108. package/src/components/ui/Grade.js +54 -0
  109. package/src/components/ui/LinkButton.js +68 -0
  110. package/src/components/ui/Loader.js +188 -0
  111. package/src/components/{Map.css → ui/Map.css} +5 -17
  112. package/src/components/{Map.js → ui/Map.js} +114 -138
  113. package/src/components/ui/MapMore.js +324 -0
  114. package/src/components/{Photo.css → ui/Photo.css} +6 -6
  115. package/src/components/{Photo.js → ui/Photo.js} +279 -90
  116. package/src/components/ui/Popup.js +145 -0
  117. package/src/components/ui/QualityScore.js +152 -0
  118. package/src/components/ui/SearchBar.js +363 -0
  119. package/src/components/ui/TogglableGroup.js +162 -0
  120. package/src/components/ui/index.js +20 -0
  121. package/src/components/ui/widgets/GeoSearch.css +21 -0
  122. package/src/components/ui/widgets/GeoSearch.js +139 -0
  123. package/src/components/ui/widgets/Legend.js +51 -0
  124. package/src/components/ui/widgets/MapFiltersButton.js +104 -0
  125. package/src/components/ui/widgets/MapLayersButton.js +79 -0
  126. package/src/components/ui/widgets/Player.css +7 -0
  127. package/src/components/ui/widgets/Player.js +148 -0
  128. package/src/components/ui/widgets/Share.js +30 -0
  129. package/src/components/ui/widgets/Zoom.js +82 -0
  130. package/src/components/ui/widgets/index.js +12 -0
  131. package/src/img/panoramax.svg +13 -0
  132. package/src/img/switch_big.svg +20 -10
  133. package/src/index.js +6 -9
  134. package/src/translations/da.json +1 -1
  135. package/src/translations/de.json +1 -1
  136. package/src/translations/en.json +5 -3
  137. package/src/translations/eo.json +1 -1
  138. package/src/translations/es.json +1 -1
  139. package/src/translations/fr.json +5 -3
  140. package/src/translations/hu.json +1 -1
  141. package/src/translations/it.json +1 -1
  142. package/src/translations/ja.json +1 -1
  143. package/src/translations/nl.json +1 -1
  144. package/src/translations/pl.json +1 -1
  145. package/src/translations/sv.json +1 -1
  146. package/src/translations/zh_Hant.json +1 -1
  147. package/src/utils/API.js +74 -42
  148. package/src/utils/InitParameters.js +354 -0
  149. package/src/utils/URLHandler.js +364 -0
  150. package/src/utils/geocoder.js +116 -0
  151. package/src/utils/{I18n.js → i18n.js} +3 -1
  152. package/src/utils/index.js +11 -0
  153. package/src/utils/{Map.js → map.js} +216 -80
  154. package/src/utils/picture.js +433 -0
  155. package/src/utils/utils.js +315 -0
  156. package/src/utils/widgets.js +93 -0
  157. package/tests/components/ui/CopyButton.test.js +52 -0
  158. package/tests/components/ui/Loader.test.js +54 -0
  159. package/tests/components/{Map.test.js → ui/Map.test.js} +19 -61
  160. package/tests/components/{Photo.test.js → ui/Photo.test.js} +89 -57
  161. package/tests/components/ui/Popup.test.js +24 -0
  162. package/tests/components/ui/QualityScore.test.js +17 -0
  163. package/tests/components/ui/SearchBar.test.js +107 -0
  164. package/tests/components/ui/__snapshots__/CopyButton.test.js.snap +34 -0
  165. package/tests/components/ui/__snapshots__/Loader.test.js.snap +56 -0
  166. package/tests/components/{__snapshots__ → ui/__snapshots__}/Map.test.js.snap +11 -38
  167. package/tests/components/{__snapshots__ → ui/__snapshots__}/Photo.test.js.snap +57 -4
  168. package/tests/components/ui/__snapshots__/Popup.test.js.snap +29 -0
  169. package/tests/components/ui/__snapshots__/QualityScore.test.js.snap +11 -0
  170. package/tests/components/ui/__snapshots__/SearchBar.test.js.snap +65 -0
  171. package/tests/utils/API.test.js +1 -14
  172. package/tests/utils/InitParameters.test.js +485 -0
  173. package/tests/utils/URLHandler.test.js +350 -0
  174. package/tests/utils/__snapshots__/URLHandler.test.js.snap +21 -0
  175. package/tests/utils/__snapshots__/picture.test.js.snap +315 -0
  176. package/tests/utils/__snapshots__/widgets.test.js.snap +19 -0
  177. package/tests/utils/geocoder.test.js +37 -0
  178. package/tests/utils/{I18n.test.js → i18n.test.js} +1 -1
  179. package/tests/utils/map.test.js +67 -0
  180. package/tests/utils/picture.test.js +745 -0
  181. package/tests/utils/utils.test.js +288 -0
  182. package/tests/utils/widgets.test.js +90 -0
  183. package/docs/01_Start.md +0 -149
  184. package/docs/02_Usage.md +0 -831
  185. package/docs/04_Advanced_examples.md +0 -216
  186. package/src/Editor.css +0 -37
  187. package/src/Editor.js +0 -361
  188. package/src/StandaloneMap.js +0 -114
  189. package/src/Viewer.css +0 -203
  190. package/src/Viewer.js +0 -1246
  191. package/src/components/CoreView.css +0 -70
  192. package/src/components/CoreView.js +0 -175
  193. package/src/components/Loader.css +0 -74
  194. package/src/components/Loader.js +0 -120
  195. package/src/utils/Exif.js +0 -193
  196. package/src/utils/Utils.js +0 -631
  197. package/src/utils/Widgets.js +0 -562
  198. package/src/viewer/URLHash.js +0 -469
  199. package/src/viewer/Widgets.css +0 -880
  200. package/src/viewer/Widgets.js +0 -1470
  201. package/tests/Editor.test.js +0 -126
  202. package/tests/StandaloneMap.test.js +0 -45
  203. package/tests/Viewer.test.js +0 -366
  204. package/tests/__snapshots__/Editor.test.js.snap +0 -298
  205. package/tests/__snapshots__/StandaloneMap.test.js.snap +0 -30
  206. package/tests/__snapshots__/Viewer.test.js.snap +0 -195
  207. package/tests/components/CoreView.test.js +0 -92
  208. package/tests/components/Loader.test.js +0 -38
  209. package/tests/components/__snapshots__/Loader.test.js.snap +0 -15
  210. package/tests/utils/Exif.test.js +0 -124
  211. package/tests/utils/Map.test.js +0 -113
  212. package/tests/utils/Utils.test.js +0 -300
  213. package/tests/utils/Widgets.test.js +0 -107
  214. package/tests/utils/__snapshots__/Exif.test.js.snap +0 -43
  215. package/tests/utils/__snapshots__/Utils.test.js.snap +0 -41
  216. package/tests/utils/__snapshots__/Widgets.test.js.snap +0 -44
  217. package/tests/viewer/URLHash.test.js +0 -559
  218. package/tests/viewer/Widgets.test.js +0 -127
  219. package/tests/viewer/__snapshots__/URLHash.test.js.snap +0 -108
  220. package/tests/viewer/__snapshots__/Widgets.test.js.snap +0 -403
  221. /package/tests/utils/__snapshots__/{Map.test.js.snap → geocoder.test.js.snap} +0 -0
@@ -1,216 +0,0 @@
1
- # Advanced examples
2
-
3
- On this page, you will discover many examples on how to do practical things, like changing the map tiles, or adding custom buttons.
4
-
5
- ## Change map background style
6
-
7
- The viewer can be configured to use a different map background than the default one. By default, an OpenStreetMap France classic style if offered. Changing the style is done by passing a `style` parameter on viewer setup. It should follow the [MapLibre Style specification](https://maplibre.org/maplibre-style-spec) and be passed as an object, or an URL to such style:
8
-
9
- ```js
10
- var viewer = new Panoramax.Viewer(
11
- "viewer",
12
- "https://my-panoramax-server.net/api",
13
- {
14
- style: "https://my.tiles.provider/basic.json",
15
- map: { startWide: true }
16
- }
17
- );
18
- ```
19
-
20
- Note that the viewer also support PMTiles (for a simpler tile hosting), so your style file can contain vector source defined like this:
21
-
22
- ```json
23
- {
24
- "sources": {
25
- "protomaps": {
26
- "type": "vector",
27
- "url": "pmtiles://https://example.com/example.pmtiles",
28
- }
29
- }
30
- }
31
- ```
32
-
33
- If you need to customize the received JSON style for compatibility issues, this can be done by passing an object instead of a string. Here is an example based on IGN map styling, which needs some parameter to be changed:
34
-
35
- ```js
36
- fetch("https://wxs.ign.fr/essentiels/static/vectorTiles/styles/PLAN.IGN/standard.json")
37
- .then(res => res.json())
38
- .then(style => {
39
- // Patch tms scheme to xyz to make it compatible for Maplibre GL JS
40
- style.sources.plan_ign.scheme = 'xyz';
41
- style.sources.plan_ign.attribution = 'Données cartographiques : © IGN';
42
-
43
- var viewer = new Panoramax.Viewer(
44
- "viewer",
45
- "https://my-panoramax-server.net/api",
46
- {
47
- style,
48
- map: { startWide: true }
49
- }
50
- );
51
- });
52
- ```
53
-
54
- ## Adding aerial imagery
55
-
56
- In complement of classic _streets_ rendering, you can add an aerial imagery as map background. This is possible using a WMS or WMTS service, and setting configuration as following (this example uses the French IGN aerial imagery):
57
-
58
- ```js
59
- var viewer = new Panoramax.Viewer(
60
- "viewer",
61
- "https://my-panoramax-server.net/api",
62
- {
63
- map: {
64
- startWide: true,
65
- raster: {
66
- type: "raster",
67
- tiles: [
68
- "https://data.geopf.fr/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ORTHOIMAGERY.ORTHOPHOTOS&STYLE=normal&FORMAT=image/jpeg&TILEMATRIXSET=PM_0_21&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}"
69
- ],
70
- minzoom: 0,
71
- maxzoom: 21,
72
- attribution: "© IGN",
73
- tileSize: 256
74
- }
75
- }
76
- }
77
- );
78
- ```
79
-
80
- ## Use another geocoder
81
-
82
- The map offers a search bar for easily locating places based on user text search. This is handled by [MapLibre GL Geocoder](https://github.com/maplibre/maplibre-gl-geocoder). By default, the viewer uses [Nominatim](https://nominatim.org/) API, which provides geocoding using OpenStreetMap data.
83
-
84
- You can switch to using another geocoder though, we also directly offer the [Base adresse nationale](https://adresse.data.gouv.fr/) API (French authority geocoder) that you can use like this:
85
-
86
- ```js
87
- var viewer = new Panoramax.Viewer(
88
- "viewer",
89
- "https://my-panoramax-server.net/api",
90
- {
91
- map: {
92
- geocoder: { engine: "ban" }
93
- }
94
- }
95
- );
96
- ```
97
-
98
- And you can also define your own custom geocoder using these options:
99
-
100
- ```js
101
- var myOwnGeocoder = function(config) {
102
- // Call your API
103
- // Config parameter is based on geocoderApi.forwardGeocode.config structure
104
- // Described here : https://github.com/maplibre/maplibre-gl-geocoder/blob/main/API.md#setgeocoderapi
105
-
106
- // It returns a promise resolving on a Carmen GeoJSON FeatureCollection
107
- // Format is described here : https://docs.mapbox.com/api/search/geocoding/#geocoding-response-object
108
- }
109
-
110
- var viewer = new Panoramax.Viewer(
111
- "viewer",
112
- "https://my-panoramax-server.net/api",
113
- {
114
- map: {
115
- geocoder: { geocoderApi: {
116
- forwardGeocode: myOwnGeocoder
117
- } }
118
- }
119
- }
120
- );
121
- ```
122
-
123
- ## Authentication against API
124
-
125
- If the STAC API you're using needs some kind of authentication, you can pass it through Web Viewer options. Parameter `fetchOptions` allows you to set custom parameters for the [JS fetch function](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters), like the `credentials` setting. For example:
126
-
127
- ```js
128
- var viewer = new Panoramax.Viewer(
129
- "viewer",
130
- "https://your-secured-stac.fr/api",
131
- {
132
- fetchOptions: {
133
- credentials: "include"
134
- }
135
- }
136
- );
137
- ```
138
-
139
- ## Add custom buttons
140
-
141
- The viewer allows you to add a custom widget, placed just over the _Share_ button (bottom-right corner). It can be defined through `widgets.customWidget` option. Here's an example to add a simple link:
142
-
143
- ```js
144
- var viewer = new Panoramax.Viewer(
145
- "viewer",
146
- "https://my-panoramax-server.net/api",
147
- {
148
- widgets: {
149
- customWidget: `<a
150
- href="https://my-amazing-page.net/"
151
- class="gvs-btn gvs-widget-bg gvs-btn-large"
152
- title="Go to an amazing page">🤩</a>`
153
- },
154
- }
155
- );
156
- ```
157
-
158
- You can also pass more complex DOM elements:
159
-
160
- ```js
161
- var myWidget = document.createElement("div");
162
- myWidget.innerHTML = "...";
163
-
164
- var viewer = new Panoramax.Viewer(
165
- "viewer",
166
- "https://my-panoramax-server.net/api",
167
- {
168
- widgets: { customWidget: myWidget }
169
- }
170
- );
171
- ```
172
-
173
-
174
- ## Coverage map synced with external component
175
-
176
- Let's say you want to list all sequences of an user. You can display a standalone map which can be synced with your custom list.
177
-
178
- ```js
179
- var map = new Panoramax.StandaloneMap(
180
- "map",
181
- "https://panoramax.ign.fr/api",
182
- {
183
- // Optional, to allow filtering by user
184
- users: ["79b851b4-232a-4c96-ac1b-b6cf693c77ae"]
185
- }
186
- );
187
-
188
- // Change visible map area
189
- map.fitBounds([west, south, east, north]);
190
-
191
- // Listen to user clicks on map
192
- map.addEventListener("select", e => {
193
- console.log("Selected sequence", e.detail.seqId, "picture", e.detail.picId);
194
- });
195
-
196
- // Listen to sequence hovered on map
197
- map.addEventListener("hover", e => {
198
- console.log("Hovered sequence", e.detail.seqId);
199
- });
200
-
201
- // You can also programatically change selection on map
202
- map.select(
203
- "c463d190-06b0-47fb-98a8-b4a775a39ad6", // A sequence ID
204
- "bdea1eb4-4496-46da-a4d5-b22b16e75fa8" // A picture ID (can be null if unknown)
205
- );
206
- ```
207
-
208
-
209
- ## Clean-up in Single Page Application
210
-
211
- If you're running the viewer in a Single Page Application (SPA) and want to get rid of it, you must destroy properly the component before changing view. This allows the viewer to properly remove all its event listeners and free memory.
212
-
213
- ```js
214
- viewer.destroy();
215
- delete viewer;
216
- ```
package/src/Editor.css DELETED
@@ -1,37 +0,0 @@
1
- /* General layout */
2
- .gvs-editor {
3
- display: flex;
4
- flex-direction: column;
5
- }
6
-
7
- .gvs-editor .gvs-map,
8
- .gvs-editor .gvs-psv {
9
- height: 50%;
10
- }
11
-
12
- /* Map background widget */
13
- .gvs-editor #gvs-map-bg {
14
- display: flex;
15
- gap: 20px;
16
- justify-content: space-evenly;
17
- margin: 10px;
18
- padding: 7px;
19
- width: unset;
20
- border-radius: 10px;
21
- position: absolute;
22
- bottom: 0;
23
- }
24
- .gvs-editor #gvs-map-bg input { display: none; }
25
- .gvs-editor #gvs-map-bg label { cursor: pointer; }
26
-
27
- .gvs-editor #gvs-map-bg img {
28
- width: 32px;
29
- border-radius: 5px;
30
- vertical-align: middle;
31
- margin-right: 5px;
32
- border: 2px solid var(--widget-bg);
33
- }
34
-
35
- .gvs-editor #gvs-map-bg input:checked + label img {
36
- outline: 3px solid var(--widget-border-btn);
37
- }
package/src/Editor.js DELETED
@@ -1,361 +0,0 @@
1
- import "./Editor.css";
2
- import CoreView from "./components/CoreView";
3
- import Map from "./components/Map";
4
- import Photo from "./components/Photo";
5
- import BackgroundAerial from "./img/bg_aerial.jpg";
6
- import BackgroundStreets from "./img/bg_streets.jpg";
7
- import { linkMapAndPhoto, apiFeatureToPSVNode } from "./utils/Utils";
8
- import { VECTOR_STYLES } from "./utils/Map";
9
- import { SYSTEM as PSSystem } from "@photo-sphere-viewer/core";
10
-
11
- const LAYER_HEADING_ID = "sequence-headings";
12
-
13
- /**
14
- * Editor allows to focus on a single sequence, and preview what you edits would look like.
15
- * It shows both picture and map.
16
- *
17
- * Note that you can use any of the [CoreView](#CoreView) class functions as well.
18
- *
19
- * @param {string|Element} container The DOM element to create viewer into
20
- * @param {string} endpoint URL to API to use (must be a [STAC API](https://github.com/radiantearth/stac-api-spec/blob/main/overview.md))
21
- * @param {object} [options] View options.
22
- * @param {string} options.selectedSequence The ID of sequence to highlight on load. Must be always defined.
23
- * @param {string} [options.selectedPicture] The ID of picture to highlight on load (defaults to none)
24
- * @param {object} [options.fetchOptions=null] Set custom options for fetch calls made against API ([same syntax as fetch options parameter](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters))
25
- * @param {object} [options.raster] The MapLibre raster source for aerial background. This must be a JSON object following [MapLibre raster source definition](https://maplibre.org/maplibre-style-spec/sources/#raster).
26
- * @param {string} [options.background] Choose default map background to display (streets or aerial, if raster aerial background available). Defaults to street.
27
- * @param {string|object} [options.style] The map's MapLibre style. This can be an a JSON object conforming to the schema described in the [MapLibre Style Specification](https://maplibre.org/maplibre-gl-js-docs/style-spec/), or a URL string pointing to one.
28
- *
29
- * @property {Map} map The map widget
30
- * @property {Photo} psv The photo widget
31
- */
32
- export default class Editor extends CoreView {
33
- constructor(container, endpoint, options = {}){
34
- super(container, endpoint, Object.assign(options, { users: [] }));
35
-
36
- // Check sequence ID is set
37
- if(!this._selectedSeqId) { this._loader.dismiss({}, "No sequence is selected"); }
38
-
39
- // Create sub-containers
40
- this.psvContainer = document.createElement("div");
41
- this.mapContainer = document.createElement("div");
42
- this.container.appendChild(this.psvContainer);
43
- this.container.appendChild(this.mapContainer);
44
-
45
- // Init PSV
46
- try {
47
- this.psv = new Photo(this, this.psvContainer);
48
- this.psv._myVTour.datasource.nodeResolver = this._getNode.bind(this);
49
- }
50
- catch(e) {
51
- let err = !PSSystem.isWebGLSupported ? this._t.gvs.error_webgl : this._t.gvs.error_psv;
52
- this._loader.dismiss(e, err);
53
- }
54
-
55
- // Init map
56
- this._api.onceReady().then(() => {
57
- try {
58
- this.map = new Map(this, this.mapContainer, {
59
- raster: options.raster,
60
- background: options.background,
61
- supplementaryStyle: this._createMapStyle(),
62
- zoom: 15, // Hack to avoid _initMapPosition call
63
- });
64
- linkMapAndPhoto(this);
65
- this._loadSequence();
66
- this.map.once("load", () => {
67
- if(options.raster) { this._addMapBackgroundWidget(); }
68
- this._bindPicturesEvents();
69
- });
70
-
71
- // Override picMarker setRotation for heading preview
72
- const oldRot = this.map._picMarker.setRotation.bind(this.map._picMarker);
73
- this.map._picMarker.setRotation = h => {
74
- h = this._lastRelHeading === undefined ? h : h + this._lastRelHeading - this.psv.getPictureRelativeHeading();
75
- return oldRot(h);
76
- };
77
- }
78
- catch(e) {
79
- this._loader.dismiss(e, this._t.gvs.error_psv);
80
- }
81
- });
82
-
83
- // Events
84
- this.addEventListener("select", this._onSelect.bind(this));
85
- }
86
-
87
- getClassName() {
88
- return "Editor";
89
- }
90
-
91
- /**
92
- * Create style for GeoJSON sequence data.
93
- * @private
94
- */
95
- _createMapStyle() {
96
- return {
97
- sources: {
98
- geovisio_editor_sequences: {
99
- type: "geojson",
100
- data: {"type": "FeatureCollection", "features": [] }
101
- }
102
- },
103
- layers: [
104
- {
105
- "id": "geovisio_editor_sequences",
106
- "type": "line",
107
- "source": "geovisio_editor_sequences",
108
- "layout": {
109
- ...VECTOR_STYLES.SEQUENCES.layout
110
- },
111
- "paint": {
112
- ...VECTOR_STYLES.SEQUENCES.paint
113
- },
114
- },
115
- {
116
- "id": "geovisio_editor_pictures",
117
- "type": "circle",
118
- "source": "geovisio_editor_sequences",
119
- "layout": {
120
- ...VECTOR_STYLES.PICTURES.layout
121
- },
122
- "paint": {
123
- ...VECTOR_STYLES.PICTURES.paint
124
- },
125
- }
126
- ]
127
- };
128
- }
129
-
130
- /**
131
- * Creates events handlers on pictures layer
132
- * @private
133
- */
134
- _bindPicturesEvents() {
135
- // Pictures events
136
- this.map.on("mousemove", "geovisio_editor_pictures", () => {
137
- this.map.getCanvas().style.cursor = "pointer";
138
- });
139
-
140
- this.map.on("mouseleave", "geovisio_editor_pictures", () => {
141
- this.map.getCanvas().style.cursor = "";
142
- });
143
-
144
- this.map.on("click", "geovisio_editor_pictures", this.map._onPictureClick.bind(this.map));
145
- }
146
-
147
- /**
148
- * Displays currently selected sequence on map
149
- * @private
150
- */
151
- _loadSequence() {
152
- return this._api.getSequenceItems(this._selectedSeqId).then(seq => {
153
- // Hide loader after source load
154
- this.map.once("sourcedata", () => {
155
- this.map.setPaintProperty("geovisio_editor_sequences", "line-color", this.map._getLayerColorStyle("sequences"));
156
- this.map.setPaintProperty("geovisio_editor_pictures", "circle-color", this.map._getLayerColorStyle("pictures"));
157
- this.map.setLayoutProperty("geovisio_editor_sequences", "visibility", "visible");
158
- this.map.setLayoutProperty("geovisio_editor_pictures", "visibility", "visible");
159
- this.map.once("styledata", () => this._loader.dismiss());
160
- });
161
-
162
- // Create data source
163
- this._sequenceData = seq.features;
164
- this.map.getSource("geovisio_editor_sequences").setData({
165
- "type": "FeatureCollection",
166
- "features": [
167
- {
168
- "type": "Feature",
169
- "properties": {
170
- "id": this._selectedSeqId,
171
- },
172
- "geometry":
173
- {
174
- "type": "LineString",
175
- "coordinates": seq.features.map(p => p.geometry.coordinates)
176
- }
177
- },
178
- ...seq.features.map(f => {
179
- f.properties.id = f.id;
180
- f.properties.sequences = [this._selectedSeqId];
181
- return f;
182
- })
183
- ]
184
- });
185
-
186
- // Select picture if any
187
- if(this._selectedPicId) {
188
- const pic = seq.features.find(p => p.id === this._selectedPicId);
189
- if(pic) {
190
- this.select(this._selectedSeqId, this._selectedPicId, true);
191
- this.map.jumpTo({ center: pic.geometry.coordinates, zoom: 18 });
192
- }
193
- else {
194
- console.log("Picture with ID", pic, "was not found");
195
- }
196
- }
197
- // Show area of sequence otherwise
198
- else {
199
- const bbox = [
200
- ...seq.features[0].geometry.coordinates,
201
- ...seq.features[0].geometry.coordinates
202
- ];
203
-
204
- for(let i=1; i < seq.features.length; i++) {
205
- const c = seq.features[i].geometry.coordinates;
206
- if(c[0] < bbox[0]) { bbox[0] = c[0]; }
207
- if(c[1] < bbox[1]) { bbox[1] = c[1]; }
208
- if(c[0] > bbox[2]) { bbox[2] = c[0]; }
209
- if(c[1] > bbox[3]) { bbox[3] = c[1]; }
210
- }
211
-
212
- this.map.fitBounds(bbox, {animate: false});
213
- }
214
- }).catch(e => this._loader.dismiss(e, this._t.gvs.error_api));
215
- }
216
-
217
- /**
218
- * Get the PSV node for wanted picture.
219
- *
220
- * @param {string} picId The picture ID
221
- * @returns The PSV node
222
- * @private
223
- */
224
- _getNode(picId) {
225
- const f = this._sequenceData.find(f => f.properties.id === picId);
226
- const n = f ? apiFeatureToPSVNode(f, this._t, this._isInternetFast) : null;
227
- if(n) { delete n.links; }
228
- return n;
229
- }
230
-
231
- /**
232
- * Creates the widget to switch between aerial and streets imagery
233
- * @private
234
- */
235
- _addMapBackgroundWidget() {
236
- // Container
237
- const pnlLayers = document.createElement("div");
238
- pnlLayers.id = "gvs-map-bg";
239
- pnlLayers.classList.add("gvs-panel", "gvs-widget-bg", "gvs-input-group");
240
- const onBgChange = e => this.map.setBackground(e.target.value);
241
-
242
- // Radio streets
243
- const radioBgStreets = document.createElement("input");
244
- radioBgStreets.id = "gvs-map-bg-streets";
245
- radioBgStreets.setAttribute("type", "radio");
246
- radioBgStreets.setAttribute("name", "gvs-map-bg");
247
- radioBgStreets.setAttribute("value", "streets");
248
- radioBgStreets.addEventListener("change", onBgChange);
249
- pnlLayers.appendChild(radioBgStreets);
250
-
251
- const labelBgStreets = document.createElement("label");
252
- labelBgStreets.setAttribute("for", radioBgStreets.id);
253
-
254
- const imgBgStreets = document.createElement("img");
255
- imgBgStreets.src = BackgroundStreets;
256
- imgBgStreets.alt = "";
257
-
258
- labelBgStreets.appendChild(imgBgStreets);
259
- labelBgStreets.appendChild(document.createTextNode(this._t.gvs.map_background_streets));
260
- pnlLayers.appendChild(labelBgStreets);
261
-
262
- // Radio aerial
263
- const radioBgAerial = document.createElement("input");
264
- radioBgAerial.id = "gvs-map-bg-aerial";
265
- radioBgAerial.setAttribute("type", "radio");
266
- radioBgAerial.setAttribute("name", "gvs-map-bg");
267
- radioBgAerial.setAttribute("value", "aerial");
268
- radioBgAerial.addEventListener("change", onBgChange);
269
- pnlLayers.appendChild(radioBgAerial);
270
-
271
- const labelBgAerial = document.createElement("label");
272
- labelBgAerial.setAttribute("for", radioBgAerial.id);
273
-
274
- const imgBgAerial = document.createElement("img");
275
- imgBgAerial.src = BackgroundAerial;
276
- imgBgAerial.alt = "";
277
-
278
- labelBgAerial.appendChild(imgBgAerial);
279
- labelBgAerial.appendChild(document.createTextNode(this._t.gvs.map_background_aerial));
280
- pnlLayers.appendChild(labelBgAerial);
281
-
282
- this.mapContainer.appendChild(pnlLayers);
283
-
284
- const onMapBgChange = bg => {
285
- if(bg === "aerial") { radioBgAerial.checked = true; }
286
- else { radioBgStreets.checked = true; }
287
- };
288
- this.addEventListener("map:background-changed", e => onMapBgChange(e.detail.background));
289
- onMapBgChange(this.map.getBackground());
290
- }
291
-
292
- /**
293
- * Preview on map how the new relative heading would reflect on all pictures.
294
- * This doesn't change anything on API-side, it's just a preview.
295
- *
296
- * @param {number} [relHeading] The new relative heading compared to sequence path. In degrees, between -180 and 180 (0 = front, -90 = left, 90 = right). Set to null to remove preview.
297
- */
298
- previewSequenceHeadingChange(relHeading) {
299
- const layerExists = this.map.getLayer(LAYER_HEADING_ID) !== undefined;
300
- this.map._picMarkerPreview.remove();
301
-
302
- // If no value set, remove layer
303
- if(relHeading === undefined) {
304
- delete this._lastRelHeading;
305
- if(layerExists) {
306
- this.map.setLayoutProperty(LAYER_HEADING_ID, "visibility", "none");
307
- }
308
- // Update selected picture marker
309
- if(this._selectedPicId) {
310
- this.map._picMarker.setRotation(this.psv.getXY().x);
311
- }
312
- return;
313
- }
314
-
315
- this._lastRelHeading = relHeading;
316
-
317
- // Create preview layer
318
- if(!layerExists) {
319
- this.map.addLayer({
320
- "id": LAYER_HEADING_ID,
321
- "type": "symbol",
322
- "source": "geovisio_editor_sequences",
323
- "layout": {
324
- "icon-image": "gvs-marker",
325
- "icon-overlap": "always",
326
- "icon-size": 0.8,
327
- },
328
- });
329
- }
330
-
331
- // Change heading
332
- const currentRelHeading = - this.psv.getPictureRelativeHeading();
333
- this.map.setLayoutProperty(LAYER_HEADING_ID, "visibility", "visible");
334
- this.map.setLayoutProperty(
335
- LAYER_HEADING_ID,
336
- "icon-rotate",
337
- ["+", ["get", "view:azimuth"], currentRelHeading, relHeading ]
338
- );
339
-
340
- // Skip selected picture and linestring geom
341
- const filters = [["==", ["geometry-type"], "Point"]];
342
- if(this._selectedPicId) { filters.push(["!=", ["get", "id"], this._selectedPicId]); }
343
- this.map.setFilter(LAYER_HEADING_ID, ["all", ...filters]);
344
-
345
- // Update selected picture marker
346
- if(this._selectedPicId) {
347
- this.map._picMarker.setRotation(this.psv.getXY().x);
348
- }
349
- }
350
-
351
- /**
352
- * Event handler for picture loading
353
- * @private
354
- */
355
- _onSelect() {
356
- // Update preview of heading change
357
- if(this._lastRelHeading !== undefined) {
358
- this.previewSequenceHeadingChange(this._lastRelHeading);
359
- }
360
- }
361
- }