@vcmap/core 5.0.0-rc.0

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 (146) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +44 -0
  3. package/build/postinstall.js +44 -0
  4. package/index.js +139 -0
  5. package/package.json +92 -0
  6. package/src/cesium/cesium3DTileFeature.js +9 -0
  7. package/src/cesium/cesium3DTilePointFeature.js +9 -0
  8. package/src/cesium/cesiumVcsCameraPrimitive.js +146 -0
  9. package/src/cesium/wallpaperMaterial.js +64 -0
  10. package/src/ol/feature.js +47 -0
  11. package/src/ol/geom/circle.js +24 -0
  12. package/src/ol/geom/geometryCollection.js +33 -0
  13. package/src/ol/render/canvas/canvasTileRenderer.js +179 -0
  14. package/src/ol/source/ClusterEnhancedVectorSource.js +39 -0
  15. package/src/ol/source/VcsCluster.js +37 -0
  16. package/src/vcs/vcm/classRegistry.js +106 -0
  17. package/src/vcs/vcm/event/vcsEvent.js +89 -0
  18. package/src/vcs/vcm/globalCollections.js +11 -0
  19. package/src/vcs/vcm/interaction/abstractInteraction.js +149 -0
  20. package/src/vcs/vcm/interaction/coordinateAtPixel.js +102 -0
  21. package/src/vcs/vcm/interaction/eventHandler.js +425 -0
  22. package/src/vcs/vcm/interaction/featureAtPixelInteraction.js +286 -0
  23. package/src/vcs/vcm/interaction/featureProviderInteraction.js +54 -0
  24. package/src/vcs/vcm/interaction/interactionChain.js +124 -0
  25. package/src/vcs/vcm/interaction/interactionType.js +114 -0
  26. package/src/vcs/vcm/layer/buildings.js +17 -0
  27. package/src/vcs/vcm/layer/cesium/cesiumTilesetCesium.js +359 -0
  28. package/src/vcs/vcm/layer/cesium/clusterContext.js +95 -0
  29. package/src/vcs/vcm/layer/cesium/dataSourceCesium.js +171 -0
  30. package/src/vcs/vcm/layer/cesium/openStreetMapCesium.js +29 -0
  31. package/src/vcs/vcm/layer/cesium/pointCloudCesium.js +58 -0
  32. package/src/vcs/vcm/layer/cesium/rasterLayerCesium.js +110 -0
  33. package/src/vcs/vcm/layer/cesium/singleImageCesium.js +49 -0
  34. package/src/vcs/vcm/layer/cesium/terrainCesium.js +80 -0
  35. package/src/vcs/vcm/layer/cesium/tmsCesium.js +54 -0
  36. package/src/vcs/vcm/layer/cesium/vectorCesium.js +255 -0
  37. package/src/vcs/vcm/layer/cesium/vectorContext.js +167 -0
  38. package/src/vcs/vcm/layer/cesium/vectorRasterTileCesium.js +116 -0
  39. package/src/vcs/vcm/layer/cesium/vectorTileImageryProvider.js +246 -0
  40. package/src/vcs/vcm/layer/cesium/wmsCesium.js +71 -0
  41. package/src/vcs/vcm/layer/cesium/wmtsCesium.js +101 -0
  42. package/src/vcs/vcm/layer/cesium/x3dmHelper.js +22 -0
  43. package/src/vcs/vcm/layer/cesiumTileset.js +376 -0
  44. package/src/vcs/vcm/layer/czml.js +141 -0
  45. package/src/vcs/vcm/layer/dataSource.js +259 -0
  46. package/src/vcs/vcm/layer/featureLayer.js +261 -0
  47. package/src/vcs/vcm/layer/featureStore.js +647 -0
  48. package/src/vcs/vcm/layer/featureStoreChanges.js +360 -0
  49. package/src/vcs/vcm/layer/featureStoreState.js +19 -0
  50. package/src/vcs/vcm/layer/featureVisibility.js +435 -0
  51. package/src/vcs/vcm/layer/geojson.js +185 -0
  52. package/src/vcs/vcm/layer/geojsonHelpers.js +450 -0
  53. package/src/vcs/vcm/layer/globalHider.js +157 -0
  54. package/src/vcs/vcm/layer/layer.js +752 -0
  55. package/src/vcs/vcm/layer/layerImplementation.js +102 -0
  56. package/src/vcs/vcm/layer/layerState.js +17 -0
  57. package/src/vcs/vcm/layer/layerSymbols.js +6 -0
  58. package/src/vcs/vcm/layer/oblique/layerOblique.js +76 -0
  59. package/src/vcs/vcm/layer/oblique/obliqueHelpers.js +175 -0
  60. package/src/vcs/vcm/layer/oblique/vectorOblique.js +469 -0
  61. package/src/vcs/vcm/layer/openStreetMap.js +194 -0
  62. package/src/vcs/vcm/layer/openlayers/layerOpenlayers.js +79 -0
  63. package/src/vcs/vcm/layer/openlayers/openStreetMapOpenlayers.js +27 -0
  64. package/src/vcs/vcm/layer/openlayers/rasterLayerOpenlayers.js +121 -0
  65. package/src/vcs/vcm/layer/openlayers/singleImageOpenlayers.js +49 -0
  66. package/src/vcs/vcm/layer/openlayers/tileDebugOpenlayers.js +39 -0
  67. package/src/vcs/vcm/layer/openlayers/tmsOpenlayers.js +62 -0
  68. package/src/vcs/vcm/layer/openlayers/vectorOpenlayers.js +118 -0
  69. package/src/vcs/vcm/layer/openlayers/vectorTileOpenlayers.js +177 -0
  70. package/src/vcs/vcm/layer/openlayers/wmsOpenlayers.js +55 -0
  71. package/src/vcs/vcm/layer/openlayers/wmtsOpenlayers.js +141 -0
  72. package/src/vcs/vcm/layer/pointCloud.js +162 -0
  73. package/src/vcs/vcm/layer/rasterLayer.js +294 -0
  74. package/src/vcs/vcm/layer/singleImage.js +119 -0
  75. package/src/vcs/vcm/layer/terrain.js +122 -0
  76. package/src/vcs/vcm/layer/terrainHelpers.js +123 -0
  77. package/src/vcs/vcm/layer/tileLoadedHelper.js +72 -0
  78. package/src/vcs/vcm/layer/tileProvider/mvtTileProvider.js +104 -0
  79. package/src/vcs/vcm/layer/tileProvider/staticGeojsonTileProvider.js +67 -0
  80. package/src/vcs/vcm/layer/tileProvider/tileProvider.js +584 -0
  81. package/src/vcs/vcm/layer/tileProvider/tileProviderFactory.js +28 -0
  82. package/src/vcs/vcm/layer/tileProvider/urlTemplateTileProvider.js +106 -0
  83. package/src/vcs/vcm/layer/tms.js +121 -0
  84. package/src/vcs/vcm/layer/vector.js +632 -0
  85. package/src/vcs/vcm/layer/vectorHelpers.js +206 -0
  86. package/src/vcs/vcm/layer/vectorProperties.js +1391 -0
  87. package/src/vcs/vcm/layer/vectorSymbols.js +40 -0
  88. package/src/vcs/vcm/layer/vectorTile.js +480 -0
  89. package/src/vcs/vcm/layer/wfs.js +165 -0
  90. package/src/vcs/vcm/layer/wms.js +270 -0
  91. package/src/vcs/vcm/layer/wmsHelpers.js +65 -0
  92. package/src/vcs/vcm/layer/wmts.js +235 -0
  93. package/src/vcs/vcm/maps/baseOLMap.js +257 -0
  94. package/src/vcs/vcm/maps/cameraLimiter.js +219 -0
  95. package/src/vcs/vcm/maps/cesium.js +1192 -0
  96. package/src/vcs/vcm/maps/map.js +511 -0
  97. package/src/vcs/vcm/maps/mapState.js +17 -0
  98. package/src/vcs/vcm/maps/oblique.js +536 -0
  99. package/src/vcs/vcm/maps/openlayers.js +205 -0
  100. package/src/vcs/vcm/object.js +92 -0
  101. package/src/vcs/vcm/oblique/ObliqueCollection.js +572 -0
  102. package/src/vcs/vcm/oblique/ObliqueDataSet.js +357 -0
  103. package/src/vcs/vcm/oblique/ObliqueImage.js +247 -0
  104. package/src/vcs/vcm/oblique/ObliqueImageMeta.js +126 -0
  105. package/src/vcs/vcm/oblique/ObliqueProvider.js +433 -0
  106. package/src/vcs/vcm/oblique/ObliqueView.js +130 -0
  107. package/src/vcs/vcm/oblique/ObliqueViewDirection.js +40 -0
  108. package/src/vcs/vcm/oblique/helpers.js +483 -0
  109. package/src/vcs/vcm/oblique/parseImageJson.js +248 -0
  110. package/src/vcs/vcm/util/clipping/clippingObject.js +386 -0
  111. package/src/vcs/vcm/util/clipping/clippingObjectManager.js +312 -0
  112. package/src/vcs/vcm/util/clipping/clippingPlaneHelper.js +413 -0
  113. package/src/vcs/vcm/util/collection.js +193 -0
  114. package/src/vcs/vcm/util/dateTime.js +60 -0
  115. package/src/vcs/vcm/util/exclusiveManager.js +135 -0
  116. package/src/vcs/vcm/util/extent.js +124 -0
  117. package/src/vcs/vcm/util/featureProvider/abstractFeatureProvider.js +196 -0
  118. package/src/vcs/vcm/util/featureProvider/featureProviderHelpers.js +51 -0
  119. package/src/vcs/vcm/util/featureProvider/featureProviderSymbols.js +11 -0
  120. package/src/vcs/vcm/util/featureProvider/tileProviderFeatureProvider.js +62 -0
  121. package/src/vcs/vcm/util/featureProvider/wmsFeatureProvider.js +280 -0
  122. package/src/vcs/vcm/util/featureconverter/circleToCesium.js +215 -0
  123. package/src/vcs/vcm/util/featureconverter/convert.js +83 -0
  124. package/src/vcs/vcm/util/featureconverter/extent3d.js +154 -0
  125. package/src/vcs/vcm/util/featureconverter/featureconverterHelper.js +591 -0
  126. package/src/vcs/vcm/util/featureconverter/lineStringToCesium.js +171 -0
  127. package/src/vcs/vcm/util/featureconverter/pointToCesium.js +359 -0
  128. package/src/vcs/vcm/util/featureconverter/polygonToCesium.js +229 -0
  129. package/src/vcs/vcm/util/geometryHelpers.js +172 -0
  130. package/src/vcs/vcm/util/indexedCollection.js +158 -0
  131. package/src/vcs/vcm/util/isMobile.js +12 -0
  132. package/src/vcs/vcm/util/layerCollection.js +216 -0
  133. package/src/vcs/vcm/util/locale.js +53 -0
  134. package/src/vcs/vcm/util/mapCollection.js +363 -0
  135. package/src/vcs/vcm/util/math.js +71 -0
  136. package/src/vcs/vcm/util/projection.js +348 -0
  137. package/src/vcs/vcm/util/splitScreen.js +233 -0
  138. package/src/vcs/vcm/util/style/declarativeStyleItem.js +631 -0
  139. package/src/vcs/vcm/util/style/shapesCategory.js +67 -0
  140. package/src/vcs/vcm/util/style/styleFactory.js +48 -0
  141. package/src/vcs/vcm/util/style/styleHelpers.js +555 -0
  142. package/src/vcs/vcm/util/style/styleItem.js +226 -0
  143. package/src/vcs/vcm/util/style/vectorStyleItem.js +927 -0
  144. package/src/vcs/vcm/util/style/writeStyle.js +48 -0
  145. package/src/vcs/vcm/util/urlHelpers.js +16 -0
  146. package/src/vcs/vcm/util/viewpoint.js +333 -0
@@ -0,0 +1,126 @@
1
+ import { cartesian2DDistance } from '../util/math.js';
2
+
3
+ /**
4
+ * @typedef {Object} ObliqueImageMetaOptions
5
+ * @property {import("ol/coordinate").Coordinate|undefined} "principal-point"
6
+ * @property {import("ol/coordinate").Coordinate|undefined} "pixel-size"
7
+ * @property {Array<number>|undefined} "radial-distorsion-expected-2-found"
8
+ * @property {Array<number>|undefined} "radial-distorsion-found-2-expected"
9
+ * @property {import("ol/size").Size} size
10
+ * @property {import("ol/size").Size} tileSize
11
+ * @property {Array<number>} tileResolution
12
+ * @property {import("ol/proj/Projection").default} projection
13
+ * @property {string} url
14
+ * @property {import("@vcmap/cesium").CesiumTerrainProvider} terrainProvider
15
+ * @property {string} name
16
+ * @property {string|undefined} [format='jpg']
17
+ * @api
18
+ */
19
+
20
+
21
+ /**
22
+ * @class
23
+ * @export
24
+ */
25
+ class ObliqueImageMeta {
26
+ /**
27
+ * @param {ObliqueImageMetaOptions} options
28
+ */
29
+ constructor(options) {
30
+ /**
31
+ * The name of the camera associated with these meta data
32
+ * @type {string}
33
+ * @api
34
+ * @readonly
35
+ */
36
+ this.name = options.name;
37
+ /** @type {import("ol/coordinate").Coordinate|undefined} */
38
+ this.principalPoint = options['principal-point'];
39
+ /** @type {import("ol/coordinate").Coordinate|undefined} */
40
+ this.pixelSize = options['pixel-size'];
41
+ /** @type {Array<number>|undefined} */
42
+ this.radialE2F = options['radial-distorsion-expected-2-found'];
43
+ /** @type {Array<number>|undefined} */
44
+ this.radialF2E = options['radial-distorsion-found-2-expected'];
45
+ /** @type {boolean} */
46
+ this.hasRadial = !!(this.pixelSize && (this.radialE2F && this.radialF2E));
47
+ /**
48
+ * The size of the images associated with this meta data
49
+ * @type {import("ol/size").Size}
50
+ * @api
51
+ */
52
+ this.size = options.size;
53
+ /**
54
+ * The tile size of the images associated with this meta data
55
+ * @type {import("ol/size").Size}
56
+ * @api
57
+ */
58
+ this.tileSize = options.tileSize;
59
+ /**
60
+ * The tile resolutions of the images associated with this meta data
61
+ * @type {Array<number>}
62
+ * @api
63
+ */
64
+ this.tileResolution = options.tileResolution;
65
+ /**
66
+ * The world projection of the images associated with this meta
67
+ * @type {import("ol/proj/Projection").default}
68
+ * @api
69
+ */
70
+ this.projection = options.projection;
71
+ /**
72
+ * @type {string}
73
+ * @api
74
+ */
75
+ this.url = options.url;
76
+ /**
77
+ * An optional terrain provider
78
+ * @type {import("@vcmap/cesium").CesiumTerrainProvider}
79
+ * @api
80
+ */
81
+ this.terrainProvider = options.terrainProvider;
82
+ /**
83
+ * @type {string}
84
+ * @api
85
+ */
86
+ this.format = options.format || 'jpg';
87
+ }
88
+
89
+ /**
90
+ * Removes radial distortion in image coordinates. Radial coefficients must be provided
91
+ * @param {import("ol/coordinate").Coordinate} coordinate
92
+ * @param {boolean=} [useF2E=false] useFound2Expected, if not true expectedToFound is used
93
+ * @returns {import("ol/coordinate").Coordinate}
94
+ * @api
95
+ */
96
+ radialDistortionCoordinate(coordinate, useF2E) {
97
+ if (this.hasRadial && this.principalPoint) {
98
+ const coefficientsArray = useF2E ? this.radialF2E : this.radialE2F;
99
+
100
+ const distC2PPInMM = cartesian2DDistance(this.principalPoint, coordinate) * this.pixelSize[0];
101
+ if (distC2PPInMM === 0) {
102
+ return coordinate.slice();
103
+ }
104
+ const diffX = coordinate[0] - this.principalPoint[0];
105
+ const diffY = coordinate[1] - this.principalPoint[1];
106
+
107
+ // get shift value
108
+ let shift = 0;
109
+ for (let i = 0; i < coefficientsArray.length; ++i) {
110
+ shift += coefficientsArray[i] * (distC2PPInMM ** i);
111
+ }
112
+
113
+ // get new position through spherical coordinates system - http://mathworld.wolfram.com/SphericalCoordinates.html
114
+ const newDistInPixel = (distC2PPInMM + shift) / this.pixelSize[0];
115
+ const angleTheta = Math.atan2(diffY, diffX);
116
+ return [
117
+ this.principalPoint[0] + (newDistInPixel * Math.cos(angleTheta)),
118
+ this.principalPoint[1] + (newDistInPixel * Math.sin(angleTheta)),
119
+ ];
120
+ }
121
+
122
+ return coordinate.slice();
123
+ }
124
+ }
125
+
126
+ export default ObliqueImageMeta;
@@ -0,0 +1,433 @@
1
+ import { Event as CesiumEvent } from '@vcmap/cesium';
2
+ import { getTransform } from 'ol/proj.js';
3
+ import View from 'ol/View.js';
4
+ import { unByKey } from 'ol/Observable.js';
5
+ import { DataState } from './ObliqueDataSet.js';
6
+ import OLView from './ObliqueView.js';
7
+ import { destroyCesiumEvent, transformFromImage } from './helpers.js';
8
+ import { getHeightFromTerrainProvider } from '../layer/terrainHelpers.js';
9
+ import { mercatorProjection } from '../util/projection.js';
10
+
11
+ /**
12
+ * @typedef {Object} ObliqueViewPoint
13
+ * @property {import("ol/coordinate").Coordinate} center - in mercator
14
+ * @property {number} zoom
15
+ * @property {import("@vcmap/core").ObliqueViewDirection} direction
16
+ * @api
17
+ */
18
+
19
+ /**
20
+ * @param {number} number
21
+ * @param {number} max
22
+ * @returns {number}
23
+ */
24
+ function withinBounds(number, max) {
25
+ if (number < 0) {
26
+ return 0;
27
+ }
28
+
29
+ if (number > max) {
30
+ return max;
31
+ }
32
+ return number;
33
+ }
34
+
35
+ /**
36
+ * @class
37
+ * @export
38
+ */
39
+ class ObliqueProvider {
40
+ /**
41
+ * @param {import("ol").Map} olMap
42
+ */
43
+ constructor(olMap) {
44
+ this._active = false;
45
+ /** @type {import("@vcmap/core").ObliqueImage|string|null} */
46
+ this._loadingImage = null;
47
+ /** @type {import("ol").Map} */
48
+ this._olMap = olMap;
49
+ this._viewCache = new Map();
50
+ /**
51
+ * @type {import("@vcmap/core").ObliqueImage|null}
52
+ */
53
+ this._currentImage = null; // XXX defaultImage
54
+ /**
55
+ * @type {import("@vcmap/core").ObliqueView|null}
56
+ * @private
57
+ */
58
+ this._currentView = null;
59
+ /** @type {import("@vcmap/core").ObliqueCollection} */
60
+ this._collection = null; // XXX should we also make a default collection?
61
+ /** @type {string} */
62
+ this._mapChangeEvent = 'postrender';
63
+
64
+ /**
65
+ * Event raised once a new image is set on the provider. Will be passed the new image as the only argument.
66
+ * @type {import("@vcmap/cesium").Event}
67
+ * @api
68
+ */
69
+ this.imageChanged = new CesiumEvent();
70
+ /**
71
+ * Whether the post render handler should switch on image edge. Setting
72
+ * this to false will suspend all post render handler switches.
73
+ * @type {boolean}
74
+ * @api
75
+ */
76
+ this.switchEnabled = true;
77
+ /**
78
+ * Threshold from 0 to 1 to define when to start switching to other images. Where 0 indicates
79
+ * to only switch, when the view center is outside of the image and 1 to always switch. 0.2 would start switching
80
+ * if the view center is within the outer 20% of the image.
81
+ * @type {number}
82
+ * @api
83
+ */
84
+ this.switchThreshold = 0;
85
+ }
86
+
87
+ /**
88
+ * The event to listen to on the map. can be 'postrender' or 'moveend'. Default is 'postrender'
89
+ * @type {string}
90
+ * @api
91
+ */
92
+ get mapChangeEvent() {
93
+ return this._mapChangeEvent;
94
+ }
95
+
96
+ /**
97
+ * @param {string} eventName
98
+ */
99
+ set mapChangeEvent(eventName) {
100
+ this._mapChangeEvent = eventName;
101
+ if (this._active) {
102
+ if (this._postRenderListener) {
103
+ unByKey(this._postRenderListener);
104
+ }
105
+ // @ts-ignore
106
+ this._postRenderListener = this._olMap.on(this._mapChangeEvent, this._postRenderHandler.bind(this));
107
+ }
108
+ }
109
+
110
+ /**
111
+ * @returns {boolean}
112
+ * @api
113
+ */
114
+ get loading() {
115
+ return !!this._loadingImage;
116
+ }
117
+
118
+ /**
119
+ * @returns {boolean}
120
+ * @api
121
+ */
122
+ get active() {
123
+ return this._active;
124
+ }
125
+
126
+ /**
127
+ * @readonly
128
+ * @type {import("@vcmap/core").ObliqueImage|null}
129
+ * @api
130
+ */
131
+ get currentImage() { return this._currentImage; }
132
+
133
+ /**
134
+ * @readonly
135
+ * @type {import("@vcmap/core").ObliqueCollection|null}
136
+ * @api
137
+ */
138
+ get collection() { return this._collection; }
139
+
140
+ /**
141
+ * Set a new collection. The collection must be loaded.
142
+ * If a previous collection was set, the current image and its resources will be removed from the olMap.
143
+ * @param {import("@vcmap/core").ObliqueCollection} collection
144
+ * @api
145
+ */
146
+ setCollection(collection) {
147
+ this._loadingImage = null;
148
+ if (!collection.loaded) {
149
+ // eslint-disable-next-line no-console
150
+ console.error('cannot set an unloaded collection');
151
+ return;
152
+ }
153
+ this._collection = collection;
154
+ this._removeCurrentView();
155
+ this._currentView = null;
156
+ this._currentImage = null;
157
+ }
158
+
159
+ /**
160
+ * Activate the provider, its current view and its post render handler
161
+ * @api
162
+ */
163
+ activate() {
164
+ if (!this._collection) {
165
+ throw new Error('cannot activate provider without an oblique collection.');
166
+ }
167
+ if (!this._active) {
168
+ this._active = true;
169
+ this._setCurrentView();
170
+ if (!this._postRenderListener) {
171
+ // @ts-ignore
172
+ this._postRenderListener = this._olMap.on(this._mapChangeEvent, this._postRenderHandler.bind(this));
173
+ }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Deactivates the provider, removing the current view and post render handler from the map
179
+ * @api
180
+ */
181
+ deactivate() {
182
+ if (this._currentView) {
183
+ this._removeCurrentView();
184
+ }
185
+
186
+ if (this._postRenderListener) {
187
+ unByKey(this._postRenderListener);
188
+ this._postRenderListener = null;
189
+ }
190
+
191
+ this._active = false;
192
+ }
193
+
194
+ /**
195
+ * @param {import("ol/coordinate").Coordinate} coord
196
+ * @returns {import("ol/coordinate").Coordinate}
197
+ * @private
198
+ */
199
+ _pullCoordinateToImageCenter(coord) {
200
+ if (this.currentImage) {
201
+ const center = [this.currentImage.meta.size[0] / 2, this.currentImage.meta.size[1] / 2];
202
+ if (coord[0] < center[0]) {
203
+ coord[0] += 50;
204
+ } else {
205
+ coord[0] -= 50;
206
+ }
207
+
208
+ if (coord[1] < center[1]) {
209
+ coord[1] += 50;
210
+ } else {
211
+ coord[1] -= 50;
212
+ }
213
+ }
214
+ return coord;
215
+ }
216
+
217
+ _postRenderHandler() {
218
+ if (this._active && !this.loading && this.switchEnabled) {
219
+ const currentSize = this._currentImage ? this._currentImage.meta.size : null;
220
+ const imageCoordinates = this._olMap.getView().getCenter();
221
+ const ratioLower = this.switchThreshold; // XXX this.switchThreshold;
222
+ const ratioUpper = 1 - ratioLower;
223
+ if (
224
+ !this._currentImage || (
225
+ imageCoordinates[0] / currentSize[0] > ratioLower &&
226
+ imageCoordinates[0] / currentSize[0] < ratioUpper &&
227
+ imageCoordinates[1] / currentSize[1] > ratioLower &&
228
+ imageCoordinates[1] / currentSize[1] < ratioUpper
229
+ )
230
+ ) {
231
+ return;
232
+ }
233
+ const pulledCenter = this._pullCoordinateToImageCenter(imageCoordinates.slice());
234
+ const worldCoords = this._currentImage.transformImage2RealWorld(pulledCenter).slice(0, 2);
235
+ const transform = getTransform(this._currentImage.meta.projection, 'EPSG:3857');
236
+ const mercatorCoords = transform(worldCoords);
237
+ const buffer = 200; // XXX make configurable?
238
+ const extent = [
239
+ mercatorCoords[0] - buffer, mercatorCoords[1] - buffer,
240
+ mercatorCoords[0] + buffer, mercatorCoords[1] + buffer,
241
+ ];
242
+ const dataState = this._collection.getDataStateForExtent(extent);
243
+ if (dataState === DataState.READY) {
244
+ const image = this._collection.getImageForCoordinate(mercatorCoords, this._currentImage.viewDirection);
245
+ if (image && image.name !== this._currentImage.name) {
246
+ this._changeImage(image, imageCoordinates);
247
+ }
248
+ } else if (dataState === DataState.PENDING) {
249
+ this._collection.loadDataForExtent(extent);
250
+ }
251
+ }
252
+ }
253
+
254
+ async _changeImage(image, imageCoordinates) {
255
+ this._loadingImage = image;
256
+ const { coords } = await transformFromImage(this._currentImage, imageCoordinates);
257
+ if (this._loadingImage !== image) {
258
+ return;
259
+ }
260
+ await this.setImage(image, coords);
261
+ }
262
+
263
+ /**
264
+ * Sets the current image
265
+ * @param {import("@vcmap/core").ObliqueImage} image
266
+ * @param {import("ol/coordinate").Coordinate=} optCenter - mercator coordinates of an optional center to use. uses the images center if undefined
267
+ * @returns {Promise<boolean>}
268
+ * @api
269
+ */
270
+ async setImage(image, optCenter) {
271
+ if (!this._collection) {
272
+ throw new Error('cannot set an image without an oblique collection.');
273
+ }
274
+ this._loadingImage = image;
275
+ const isNewImage = !this._currentImage || this._currentImage.name !== image.name;
276
+ this._currentImage = image;
277
+ if (isNewImage) {
278
+ await image.calculateImageAverageHeight();
279
+ }
280
+
281
+ if (image !== this._loadingImage) {
282
+ return false;
283
+ }
284
+
285
+ let olView;
286
+ if (this._viewCache.has(image.meta)) {
287
+ olView = this._viewCache.get(image.meta);
288
+ } else {
289
+ olView = new OLView(image.meta, this._collection.viewOptions);
290
+ this._viewCache.set(image.meta, olView);
291
+ }
292
+
293
+ const previousView = this._currentView;
294
+ this._currentView = olView;
295
+ if (isNewImage) {
296
+ this._currentView.setImageName(this._currentImage.name);
297
+ }
298
+
299
+ const [width, height] = this._currentImage.meta.size;
300
+ let center = [width / 2, height / 2];
301
+ if (optCenter) {
302
+ const worldCenter = getTransform('EPSG:3857', this._currentImage.meta.projection)(optCenter.slice(0, 2));
303
+ const imageCenter = this._currentImage.transformRealWorld2Image(worldCenter, optCenter[2]);
304
+ imageCenter[0] = withinBounds(imageCenter[0], width);
305
+ imageCenter[1] = withinBounds(imageCenter[1], height);
306
+ center = imageCenter;
307
+ }
308
+ this._currentView.view.setCenter(center);
309
+
310
+ if (this._active) {
311
+ this._setCurrentView(previousView);
312
+ }
313
+
314
+ this._loadingImage = null;
315
+ if (isNewImage) {
316
+ this.imageChanged.raiseEvent(image);
317
+ }
318
+ return true;
319
+ }
320
+
321
+ /**
322
+ * @param {import("@vcmap/core").ObliqueView=} previousView
323
+ * @private
324
+ */
325
+ _setCurrentView(previousView) {
326
+ if (this._currentView) {
327
+ const isSame = previousView && previousView === this._currentView;
328
+ if (!isSame) {
329
+ if (previousView) {
330
+ this._olMap.removeLayer(previousView.layer);
331
+ }
332
+
333
+ if (this._olMap.getView() && this._olMap.getView().getResolution()) {
334
+ this._currentView.view.setResolution(this._olMap.getView().getResolution());
335
+ }
336
+
337
+ this._olMap.setView(this._currentView.view);
338
+ this._olMap.getLayers().insertAt(0, this._currentView.layer);
339
+ }
340
+ }
341
+ }
342
+
343
+ _removeCurrentView() {
344
+ if (this._currentView) {
345
+ if (this._olMap.getView() === this._currentView.view) {
346
+ this._olMap.setView(new View());
347
+ }
348
+ this._olMap.removeLayer(this._currentView.layer);
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Sets a new image based on a ground coordinate and a direction.
354
+ * @param {import("ol/coordinate").Coordinate} coordinate
355
+ * @param {import("@vcmap/core").ObliqueViewDirection} direction
356
+ * @param {number} [zoom=2]
357
+ * @returns {Promise<void>}
358
+ * @api
359
+ */
360
+ async setView(coordinate, direction, zoom = 2) {
361
+ if (!this._collection) {
362
+ throw new Error('cannot set the view without an oblique collection.');
363
+ }
364
+
365
+ const usedCoordinate = coordinate.slice();
366
+ const coordinateHash = `${coordinate.join('')}${direction}${zoom}`;
367
+ this._loadingImage = coordinateHash;
368
+ const image = await this._collection.loadImageForCoordinate(coordinate, direction);
369
+ if (image) {
370
+ if (this._loadingImage !== coordinateHash) {
371
+ return;
372
+ }
373
+ this._loadingImage = image;
374
+ if (!usedCoordinate[2] && image.meta.terrainProvider) {
375
+ const transformResult = [usedCoordinate];
376
+ await getHeightFromTerrainProvider(
377
+ image.meta.terrainProvider,
378
+ transformResult,
379
+ mercatorProjection,
380
+ transformResult,
381
+ );
382
+ }
383
+ if (this._loadingImage !== image) {
384
+ return;
385
+ }
386
+ const imageSet = await this.setImage(image, usedCoordinate);
387
+ if (imageSet) {
388
+ this._currentView.view.setZoom(zoom);
389
+ }
390
+ } else {
391
+ throw new Error('could not find an image for this direction');
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Returns a viewpoint for the currently set view.
397
+ * @returns {Promise<ObliqueViewPoint>}
398
+ * @api
399
+ */
400
+ async getView() {
401
+ if (this._currentView && this._currentImage) {
402
+ const imageCoord = this._currentView.view.getCenter();
403
+ const { coords: center } = await transformFromImage(this._currentImage, imageCoord);
404
+ return {
405
+ center,
406
+ direction: this._currentImage.viewDirection,
407
+ zoom: this._currentView.view.getZoom(),
408
+ };
409
+ }
410
+ return null;
411
+ }
412
+
413
+ /**
414
+ * Destroys all openlayers resources created by this oblique provider
415
+ * @api
416
+ */
417
+ destroy() {
418
+ this._removeCurrentView();
419
+ [...this._viewCache.values()].forEach((ov) => { ov.destroy(); });
420
+ this._viewCache.clear();
421
+ this._loadingImage = null;
422
+ if (this._postRenderListener) {
423
+ unByKey(this._postRenderListener);
424
+ this._postRenderListener = null;
425
+ }
426
+
427
+ destroyCesiumEvent(this.imageChanged);
428
+ this._collection = null;
429
+ this._olMap = null;
430
+ }
431
+ }
432
+
433
+ export default ObliqueProvider;
@@ -0,0 +1,130 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import OLProjection from 'ol/proj/Projection.js';
3
+ import View from 'ol/View.js';
4
+ import TileGrid from 'ol/tilegrid/TileGrid.js';
5
+ import TileImage from 'ol/source/TileImage.js';
6
+ import Tile from 'ol/layer/Tile.js';
7
+ import { hasSameOrigin } from './helpers.js';
8
+
9
+ /**
10
+ * @typedef {Object} ObliqueViewOptions
11
+ * @property {number} minZoom
12
+ * @property {number} maxZoom
13
+ * @property {number} scaleFactor
14
+ * @property {number} hideLevels
15
+ * @api
16
+ */
17
+
18
+ /**
19
+ * @class
20
+ * @export
21
+ */
22
+ class ObliqueView {
23
+ /**
24
+ * @param {import("@vcmap/core").ObliqueImageMeta} imageMeta
25
+ * @param {ObliqueViewOptions} options
26
+ */
27
+ constructor(imageMeta, options) {
28
+ /** @type {string} */
29
+ this.id = uuidv4();
30
+ /** @type {import("ol/size").Size} */
31
+ this.size = imageMeta.size;
32
+ /** @type {string} */
33
+ this.url = imageMeta.url;
34
+ /** @type {import("ol/size").Size} */
35
+ this.tileSize = imageMeta.tileSize;
36
+ /** @type {string} */
37
+ this.format = imageMeta.format;
38
+ /** @type {number} */
39
+ this.minZoom = options.minZoom;
40
+ /** @type {number} */
41
+ this.maxZoom = options.maxZoom;
42
+ /** @type {number} */
43
+ this.scaleFactor = options.scaleFactor;
44
+ const { tileResolution } = imageMeta;
45
+ /** @type {Array<number>} */
46
+ this.tileResolution = tileResolution.slice(0, tileResolution.length - options.hideLevels);
47
+ this._createViewAndLayer();
48
+ }
49
+
50
+ _createViewAndLayer() {
51
+ const extent = /** @type {import("ol/extent").Extent} */ ([0, 0, ...this.size]);
52
+ const zoomifyProjection = new OLProjection({
53
+ code: 'ZOOMIFY',
54
+ units: 'pixels',
55
+ extent,
56
+ });
57
+
58
+ const maxZoom = this.maxZoom > 0 ? this.maxZoom : this.tileResolution.length + 4;
59
+ const zoomMultiplier = Math.log(2) / Math.log(this.scaleFactor);
60
+
61
+ /**
62
+ * The view for these oblique images.
63
+ * @type {import("ol/View").default}
64
+ * @api
65
+ */
66
+ this.view = new View({
67
+ projection: zoomifyProjection,
68
+ center: [this.size[0] / 2, this.size[1] / 2],
69
+ constrainOnlyCenter: true,
70
+ minZoom: this.minZoom * zoomMultiplier,
71
+ maxZoom: maxZoom * zoomMultiplier,
72
+ extent: /** @type {import("ol/extent").Extent} */ ([
73
+ -2000,
74
+ -2000,
75
+ this.size[0] + 2000,
76
+ this.size[1] + 2000,
77
+ ]),
78
+ zoom: this.minZoom * zoomMultiplier,
79
+ zoomFactor: this.scaleFactor,
80
+ });
81
+
82
+ const tileImageOptions = {
83
+ projection: zoomifyProjection,
84
+ tileGrid: new TileGrid({
85
+ origin: [0, 0],
86
+ extent,
87
+ resolutions: this.tileResolution,
88
+ tileSize: this.tileSize,
89
+ }),
90
+ };
91
+ if (!hasSameOrigin(this.url)) {
92
+ tileImageOptions.crossOrigin = 'anonymous';
93
+ }
94
+ /** @type {import("ol/source/TileImage").default} */
95
+ this.tileImageSource = new TileImage(tileImageOptions);
96
+
97
+ /**
98
+ * The layer of these images.
99
+ * @type {import("ol/layer/Tile").default<import("ol/source/TileImage").default>}
100
+ * @api
101
+ */
102
+ this.layer = new Tile({
103
+ source: this.tileImageSource,
104
+ extent,
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Sets the layers source to request data for this image
110
+ * @param {string} name
111
+ * @api
112
+ */
113
+ setImageName(name) {
114
+ this.tileImageSource.setTileUrlFunction((coords) => {
115
+ const [z, x, yInverted] = coords;
116
+ const y = -yInverted - 1;
117
+ return `${this.url}/${name}/${z}/${x}/${y}.${this.format}`;
118
+ });
119
+ this.tileImageSource.refresh();
120
+ }
121
+
122
+ destroy() {
123
+ this.view = null;
124
+ this.layer = null;
125
+ this.tileImageSource.clear();
126
+ this.tileImageSource = null;
127
+ }
128
+ }
129
+
130
+ export default ObliqueView;