-
Notifications
You must be signed in to change notification settings - Fork 3.5k
/
Copy pathApproximateTerrainHeights.js
257 lines (231 loc) · 7.55 KB
/
ApproximateTerrainHeights.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
import BoundingSphere from "./BoundingSphere.js";
import buildModuleUrl from "./buildModuleUrl.js";
import Cartesian2 from "./Cartesian2.js";
import Cartesian3 from "./Cartesian3.js";
import Cartographic from "./Cartographic.js";
import Check from "./Check.js";
import defaultValue from "./defaultValue.js";
import defined from "./defined.js";
import DeveloperError from "./DeveloperError.js";
import Ellipsoid from "./Ellipsoid.js";
import GeographicTilingScheme from "./GeographicTilingScheme.js";
import Rectangle from "./Rectangle.js";
import Resource from "./Resource.js";
const scratchDiagonalCartesianNE = new Cartesian3();
const scratchDiagonalCartesianSW = new Cartesian3();
const scratchDiagonalCartographic = new Cartographic();
const scratchCenterCartesian = new Cartesian3();
const scratchSurfaceCartesian = new Cartesian3();
const scratchBoundingSphere = new BoundingSphere();
const tilingScheme = new GeographicTilingScheme();
const scratchCorners = [
new Cartographic(),
new Cartographic(),
new Cartographic(),
new Cartographic(),
];
const scratchTileXY = new Cartesian2();
/**
* A collection of functions for approximating terrain height
* @private
*/
const ApproximateTerrainHeights = {};
/**
* Initializes the minimum and maximum terrain heights
* @return {Promise<void>}
*/
ApproximateTerrainHeights.initialize = function () {
let initPromise = ApproximateTerrainHeights._initPromise;
if (defined(initPromise)) {
return initPromise;
}
initPromise = Resource.fetchJson(
buildModuleUrl("Assets/approximateTerrainHeights.json")
).then(function (json) {
ApproximateTerrainHeights._terrainHeights = json;
});
ApproximateTerrainHeights._initPromise = initPromise;
return initPromise;
};
/**
* Computes the minimum and maximum terrain heights for a given rectangle
* @param {Rectangle} rectangle The bounding rectangle
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid
* @return {{minimumTerrainHeight: Number, maximumTerrainHeight: Number}}
*/
ApproximateTerrainHeights.getMinimumMaximumHeights = function (
rectangle,
ellipsoid
) {
//>>includeStart('debug', pragmas.debug);
Check.defined("rectangle", rectangle);
if (!defined(ApproximateTerrainHeights._terrainHeights)) {
throw new DeveloperError(
"You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function"
);
}
//>>includeEnd('debug');
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
const xyLevel = getTileXYLevel(rectangle);
// Get the terrain min/max for that tile
let minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;
let maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
if (defined(xyLevel)) {
const key = `${xyLevel.level}-${xyLevel.x}-${xyLevel.y}`;
const heights = ApproximateTerrainHeights._terrainHeights[key];
if (defined(heights)) {
minTerrainHeight = heights[0];
maxTerrainHeight = heights[1];
}
// Compute min by taking the center of the NE->SW diagonal and finding distance to the surface
ellipsoid.cartographicToCartesian(
Rectangle.northeast(rectangle, scratchDiagonalCartographic),
scratchDiagonalCartesianNE
);
ellipsoid.cartographicToCartesian(
Rectangle.southwest(rectangle, scratchDiagonalCartographic),
scratchDiagonalCartesianSW
);
Cartesian3.midpoint(
scratchDiagonalCartesianSW,
scratchDiagonalCartesianNE,
scratchCenterCartesian
);
const surfacePosition = ellipsoid.scaleToGeodeticSurface(
scratchCenterCartesian,
scratchSurfaceCartesian
);
if (defined(surfacePosition)) {
const distance = Cartesian3.distance(
scratchCenterCartesian,
surfacePosition
);
minTerrainHeight = Math.min(minTerrainHeight, -distance);
} else {
minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight;
}
}
minTerrainHeight = Math.max(
ApproximateTerrainHeights._defaultMinTerrainHeight,
minTerrainHeight
);
return {
minimumTerrainHeight: minTerrainHeight,
maximumTerrainHeight: maxTerrainHeight,
};
};
/**
* Computes the bounding sphere based on the tile heights in the rectangle
* @param {Rectangle} rectangle The bounding rectangle
* @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid
* @return {BoundingSphere} The result bounding sphere
*/
ApproximateTerrainHeights.getBoundingSphere = function (rectangle, ellipsoid) {
//>>includeStart('debug', pragmas.debug);
Check.defined("rectangle", rectangle);
if (!defined(ApproximateTerrainHeights._terrainHeights)) {
throw new DeveloperError(
"You must call ApproximateTerrainHeights.initialize and wait for the promise to resolve before using this function"
);
}
//>>includeEnd('debug');
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
const xyLevel = getTileXYLevel(rectangle);
// Get the terrain max for that tile
let maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight;
if (defined(xyLevel)) {
const key = `${xyLevel.level}-${xyLevel.x}-${xyLevel.y}`;
const heights = ApproximateTerrainHeights._terrainHeights[key];
if (defined(heights)) {
maxTerrainHeight = heights[1];
}
}
const result = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0);
BoundingSphere.fromRectangle3D(
rectangle,
ellipsoid,
maxTerrainHeight,
scratchBoundingSphere
);
return BoundingSphere.union(result, scratchBoundingSphere, result);
};
function getTileXYLevel(rectangle) {
Cartographic.fromRadians(
rectangle.east,
rectangle.north,
0.0,
scratchCorners[0]
);
Cartographic.fromRadians(
rectangle.west,
rectangle.north,
0.0,
scratchCorners[1]
);
Cartographic.fromRadians(
rectangle.east,
rectangle.south,
0.0,
scratchCorners[2]
);
Cartographic.fromRadians(
rectangle.west,
rectangle.south,
0.0,
scratchCorners[3]
);
// Determine which tile the bounding rectangle is in
let lastLevelX = 0,
lastLevelY = 0;
let currentX = 0,
currentY = 0;
const maxLevel = ApproximateTerrainHeights._terrainHeightsMaxLevel;
let i;
for (i = 0; i <= maxLevel; ++i) {
let failed = false;
for (let j = 0; j < 4; ++j) {
const corner = scratchCorners[j];
tilingScheme.positionToTileXY(corner, i, scratchTileXY);
if (j === 0) {
currentX = scratchTileXY.x;
currentY = scratchTileXY.y;
} else if (currentX !== scratchTileXY.x || currentY !== scratchTileXY.y) {
failed = true;
break;
}
}
if (failed) {
break;
}
lastLevelX = currentX;
lastLevelY = currentY;
}
if (i === 0) {
return undefined;
}
return {
x: lastLevelX,
y: lastLevelY,
level: i > maxLevel ? maxLevel : i - 1,
};
}
ApproximateTerrainHeights._terrainHeightsMaxLevel = 6;
ApproximateTerrainHeights._defaultMaxTerrainHeight = 9000.0;
ApproximateTerrainHeights._defaultMinTerrainHeight = -100000.0;
ApproximateTerrainHeights._terrainHeights = undefined;
ApproximateTerrainHeights._initPromise = undefined;
Object.defineProperties(ApproximateTerrainHeights, {
/**
* Determines if the terrain heights are initialized and ready to use. To initialize the terrain heights,
* call {@link ApproximateTerrainHeights#initialize} and wait for the returned promise to resolve.
* @type {Boolean}
* @readonly
* @memberof ApproximateTerrainHeights
*/
initialized: {
get: function () {
return defined(ApproximateTerrainHeights._terrainHeights);
},
},
});
export default ApproximateTerrainHeights;