/
sampleTerrain.js
193 lines (179 loc) · 7.19 KB
/
sampleTerrain.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
import when from "../ThirdParty/when.js";
import Check from "./Check.js";
/**
* Initiates a terrain height query for an array of {@link Cartographic} positions by
* requesting tiles from a terrain provider, sampling, and interpolating. The interpolation
* matches the triangles used to render the terrain at the specified level. The query
* happens asynchronously, so this function returns a promise that is resolved when
* the query completes. Each point height is modified in place. If a height can not be
* determined because no terrain data is available for the specified level at that location,
* or another error occurs, the height is set to undefined. As is typical of the
* {@link Cartographic} type, the supplied height is a height above the reference ellipsoid
* (such as {@link Ellipsoid.WGS84}) rather than an altitude above mean sea level. In other
* words, it will not necessarily be 0.0 if sampled in the ocean. This function needs the
* terrain level of detail as input, if you need to get the altitude of the terrain as precisely
* as possible (i.e. with maximum level of detail) use {@link sampleTerrainMostDetailed}.
*
* @function sampleTerrain
*
* @param {TerrainProvider} terrainProvider The terrain provider from which to query heights.
* @param {Number} level The terrain level-of-detail from which to query terrain heights.
* @param {Cartographic[]} positions The positions to update with terrain heights.
* @returns {Promise.<Cartographic[]>} A promise that resolves to the provided list of positions when terrain the query has completed.
*
* @see sampleTerrainMostDetailed
*
* @example
* // Query the terrain height of two Cartographic positions
* var terrainProvider = Cesium.createWorldTerrain();
* var positions = [
* Cesium.Cartographic.fromDegrees(86.925145, 27.988257),
* Cesium.Cartographic.fromDegrees(87.0, 28.0)
* ];
* var promise = Cesium.sampleTerrain(terrainProvider, 11, positions);
* Cesium.when(promise, function(updatedPositions) {
* // positions[0].height and positions[1].height have been updated.
* // updatedPositions is just a reference to positions.
* });
*/
function sampleTerrain(terrainProvider, level, positions) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("terrainProvider", terrainProvider);
Check.typeOf.number("level", level);
Check.defined("positions", positions);
//>>includeEnd('debug');
return terrainProvider.readyPromise.then(function () {
return doSampling(terrainProvider, level, positions);
});
}
function doSampling(terrainProvider, level, positions) {
var tilingScheme = terrainProvider.tilingScheme;
var i;
// Sort points into a set of tiles
var tileRequests = []; // Result will be an Array as it's easier to work with
var tileRequestSet = {}; // A unique set
for (i = 0; i < positions.length; ++i) {
var xy = tilingScheme.positionToTileXY(positions[i], level);
var key = xy.toString();
if (!tileRequestSet.hasOwnProperty(key)) {
// When tile is requested for the first time
var value = {
x: xy.x,
y: xy.y,
level: level,
tilingScheme: tilingScheme,
terrainProvider: terrainProvider,
positions: [],
};
tileRequestSet[key] = value;
tileRequests.push(value);
}
// Now append to array of points for the tile
tileRequestSet[key].positions.push(positions[i]);
}
// Send request for each required tile
var tilePromises = [];
for (i = 0; i < tileRequests.length; ++i) {
var tileRequest = tileRequests[i];
var requestPromise = tileRequest.terrainProvider.requestTileGeometry(
tileRequest.x,
tileRequest.y,
tileRequest.level
);
var tilePromise = requestPromise
.then(createInterpolateFunction(tileRequest))
.otherwise(createMarkFailedFunction(tileRequest));
tilePromises.push(tilePromise);
}
return when.all(tilePromises, function () {
return positions;
});
}
/**
* Calls {@link TerrainData#interpolateHeight} on a given {@link TerrainData} for a given {@link Cartographic} and
* will assign the height property if the return value is not undefined.
*
* If the return value is false; it's suggesting that you should call {@link TerrainData#createMesh} first.
* @param {Cartographic} position The position to interpolate for and assign the height value to
* @param {TerrainData} terrainData
* @param {Rectangle} rectangle
* @returns {Boolean} If the height was actually interpolated and assigned
* @private
*/
function interpolateAndAssignHeight(position, terrainData, rectangle) {
var height = terrainData.interpolateHeight(
rectangle,
position.longitude,
position.latitude
);
if (height === undefined) {
// if height comes back as undefined, it may implicitly mean the terrain data
// requires us to call TerrainData.createMesh() first (ArcGIS requires this in particular)
// so we'll return false and do that next!
return false;
}
position.height = height;
return true;
}
function createInterpolateFunction(tileRequest) {
var tilePositions = tileRequest.positions;
var rectangle = tileRequest.tilingScheme.tileXYToRectangle(
tileRequest.x,
tileRequest.y,
tileRequest.level
);
return function (terrainData) {
var isMeshRequired = false;
for (var i = 0; i < tilePositions.length; ++i) {
var position = tilePositions[i];
var isHeightAssigned = interpolateAndAssignHeight(
position,
terrainData,
rectangle
);
// we've found a position which returned undefined - hinting to us
// that we probably need to create a mesh for this terrain data.
// so break out of this loop and create the mesh - then we'll interpolate all the heights again
if (!isHeightAssigned) {
isMeshRequired = true;
break;
}
}
if (!isMeshRequired) {
// all position heights were interpolated - we don't need the mesh
return when.resolve();
}
// create the mesh - and interpolate all the positions again
return terrainData
.createMesh({
tilingScheme: tileRequest.tilingScheme,
x: tileRequest.x,
y: tileRequest.y,
level: tileRequest.level,
// interpolateHeight will divide away the exaggeration - so passing in 1 is fine; it doesn't really matter
exaggeration: 1,
// don't throttle this mesh creation because we've asked to sample these points;
// so sample them! We don't care how many tiles that is!
throttle: false,
})
.then(function () {
// mesh has been created - so go through every position (maybe again)
// and re-interpolate the heights - presumably using the mesh this time
for (var i = 0; i < tilePositions.length; ++i) {
var position = tilePositions[i];
// if it doesn't work this time - that's fine, we tried.
interpolateAndAssignHeight(position, terrainData, rectangle);
}
});
};
}
function createMarkFailedFunction(tileRequest) {
var tilePositions = tileRequest.positions;
return function () {
for (var i = 0; i < tilePositions.length; ++i) {
var position = tilePositions[i];
position.height = undefined;
}
};
}
export default sampleTerrain;