Permalink
Browse files

Merge pull request #559 from NICTA/terrainSample

Terrain sample
  • Loading branch information...
2 parents c8b3f26 + a533c9f commit f4816336a3639675f34f6d14ad31cc9face933f5 @pjcozzi pjcozzi committed Mar 21, 2013
@@ -31,6 +31,7 @@
<div id="toolbar">
<div id="terrainMenu"></div>
<div id="zoomButtons"></div>
+<div id="sampleButtons"></div>
</div>
<script id="cesium_sandcastle_script">
require([
@@ -134,6 +135,77 @@
}
}
}).placeAt('zoomButtons');
+
+ var terrainSamplePositions;
+ var billboards;
+ var labels;
+
+ function sampleTerrainSuccess() {
+ var ellipsoid = Cesium.Ellipsoid.WGS84;
+
+ if (typeof billboards === 'undefined') {
+ billboards = new Cesium.BillboardCollection();
+
+ Cesium.when(Cesium.loadImage('../images/facility.gif'), function(image) {
+ var textureAtlas = scene.getContext().createTextureAtlas({images : [image]});
+ billboards.setTextureAtlas(textureAtlas);
+ });
+ } else {
+ billboards.removeAll();
+ }
+
+ if (typeof labels === 'undefined') {
+ labels = new Cesium.LabelCollection();
+ } else {
+ labels.removeAll();
+ }
+
+ for( var i = 0; i < terrainSamplePositions.length; ++i ){
+ var position = terrainSamplePositions[i];
+ billboards.add({
+ position : ellipsoid.cartographicToCartesian(position),
+ verticalOrigin : Cesium.VerticalOrigin.BOTTOM,
+ scale : 0.7,
+ imageIndex : 0
+ });
+
+ labels.add({
+ position : ellipsoid.cartographicToCartesian(position),
+ text : position.height.toFixed(1),
+ horizontalOrigin : Cesium.HorizontalOrigin.CENTER,
+ scale : 0.3,
+ pixelOffset : new Cesium.Cartesian2(0,14),
+ fillColor : new Cesium.Color(1,0,0),
+ outlineColor : Cesium.Color.WHITE
+ });
+
+ }
+ scene.getPrimitives().add(billboards);
+ scene.getPrimitives().add(labels);
+ }
+
+ new Button({
+ label: 'Sample Everest Terrain',
+ onClick: function() {
+ var gridWidth = 41;
+ var gridHeight = 41;
+ var everestLatitude = Cesium.Math.toRadians(27.988257);
+ var everestLongitude = Cesium.Math.toRadians(86.925145);
+ var extentHalfSize = 0.005;
+ var e = new Cesium.Extent(everestLongitude - extentHalfSize,everestLatitude - extentHalfSize,everestLongitude + extentHalfSize,everestLatitude + extentHalfSize);
+ terrainSamplePositions = [];
+ for (var y = 0; y < gridHeight; ++y) {
+ for (var x = 0; x < gridWidth; ++x) {
+ var longitude = Cesium.Math.lerp(e.west, e.east, x / (gridWidth - 1));
+ var latitude = Cesium.Math.lerp(e.south, e.north, y / (gridHeight - 1));
+ var position = new Cesium.Cartographic(longitude, latitude);
+ terrainSamplePositions.push(position);
+ }
+ }
+
+ Cesium.when(Cesium.sampleTerrain(centralBody.terrainProvider, 11, terrainSamplePositions), sampleTerrainSuccess);
+ }
+ }).placeAt('sampleButtons');
}
var widget = new CesiumViewerWidget();
@@ -143,6 +215,7 @@
var scene = widget.scene;
var centralBody = scene.getPrimitives().getCentralBody();
+ centralBody.depthTestAgainstTerrain = true;
var cesiumTerrainProvider = new Cesium.CesiumTerrainProvider({
url : 'http://cesium.agi.com/smallterrain',
View
@@ -16,6 +16,7 @@ Beta Releases
* Added `DynamicPath.resolution` property for setting the maximum step size, in seconds, to take when sampling a position for path visualization.
* Added `TileCoordinatesImageryProvider` that renders imagery with tile X, Y, Level coordinates on the surface of the globe. This is mostly useful for debugging.
* Added `DynamicEllipse` and `DynamicObject.ellipse` property to render CZML ellipses on the globe.
+* Added `sampleTerrain` function to sample the terrain height of a list of `Cartographic` positions.
### b14 - 2013-03-01
@@ -82,11 +82,14 @@ define([
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
+ * @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
+ * or false if the request should be initiated regardless of the number of requests
+ * already in progress.
* @returns {Promise|TerrainData} A promise for the requested geometry. If this method
* returns undefined instead of a promise, it is an indication that too many requests are already
* pending and the request will be retried later.
*/
- CesiumTerrainProvider.prototype.requestTileGeometry = function(x, y, level) {
+ CesiumTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) {
var yTiles = this._tilingScheme.getNumberOfYTilesAtLevel(level);
var url = this._url + '/' + level + '/' + x + '/' + (yTiles - y - 1) + '.terrain';
@@ -95,9 +98,16 @@ define([
url = proxy.getURL(url);
}
- var promise = throttleRequestByServer(url, loadArrayBuffer);
- if (typeof promise === 'undefined') {
- return undefined;
+ var promise;
+
+ throttleRequests = defaultValue(throttleRequests, true);
+ if (throttleRequests) {
+ promise = throttleRequestByServer(url, loadArrayBuffer);
+ if (typeof promise === 'undefined') {
+ return undefined;
+ }
+ } else {
+ promise = loadArrayBuffer(url);
}
var that = this;
@@ -66,11 +66,14 @@ define([
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
+ * @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
+ * or false if the request should be initiated regardless of the number of requests
+ * already in progress.
* @returns {Promise|TerrainData} A promise for the requested geometry. If this method
* returns undefined instead of a promise, it is an indication that too many requests are already
* pending and the request will be retried later.
*/
- EllipsoidTerrainProvider.prototype.requestTileGeometry = function(x, y, level) {
+ EllipsoidTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) {
return this._terrainData;
};
@@ -196,6 +196,39 @@ define([
};
/**
+ * Computes the terrain height at a specified longitude and latitude.
+ *
+ * @memberof HeightmapTerrainData
+ *
+ * @param {Extent} extent The extent covered by this terrain data.
+ * @param {Number} longitude The longitude in radians.
+ * @param {Number} latitude The latitude in radians.
+ * @returns {Number} The terrain height at the specified position. If the position
+ * is outside the extent, this method will extrapolate the height, which is likely to be wildly
+ * incorrect for positions far outside the extent.
+ */
+ HeightmapTerrainData.prototype.interpolateHeight = function(extent, longitude, latitude) {
+ var width = this._width;
+ var height = this._height;
+
+ var heightSample;
+
+ var structure = this._structure;
+ var stride = structure.stride;
+ if (stride > 1) {
+ var elementsPerHeight = structure.elementsPerHeight;
+ var elementMultiplier = structure.elementMultiplier;
+ var isBigEndian = structure.isBigEndian;
+
+ heightSample = interpolateHeightWithStride(this._buffer, elementsPerHeight, elementMultiplier, stride, isBigEndian, extent, width, height, longitude, latitude);
+ } else {
+ heightSample = interpolateHeight(this._buffer, extent, width, height, longitude, latitude);
+ }
+
+ return heightSample * structure.heightScale + structure.heightOffset;
+ };
+
+ /**
* Upsamples this terrain data for use by a descendant tile. The resulting instance will contain a subset of the
* height samples in this instance, interpolated if necessary.
*
@@ -17,6 +17,22 @@ define([
};
/**
+ * Computes the terrain height at a specified longitude and latitude.
+ *
+ * @memberof TerrainData
+ *
+ * @param {Extent} extent The extent covered by this terrain data.
+ * @param {Number} longitude The longitude in radians.
+ * @param {Number} latitude The latitude in radians.
+ * @returns {Number} The terrain height at the specified position. If the position
+ * is outside the extent, this method will extrapolate the height, which is likely to be wildly
+ * incorrect for positions far outside the extent.
+ */
+ TerrainData.prototype.interpolateHeight = function(extent, longitude, latitude) {
+ throw new DeveloperError('This type should not be instantiated directly.');
+ };
+
+ /**
* Determines if a given child tile is available, based on the
* {@link TerrainData#childTileMask}. The given child tile coordinates are assumed
* to be one of the four children of this tile. If non-child tile coordinates are
@@ -178,11 +178,14 @@ define([
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
+ * @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
+ * or false if the request should be initiated regardless of the number of requests
+ * already in progress.
* @returns {Promise|TerrainData} A promise for the requested geometry. If this method
* returns undefined instead of a promise, it is an indication that too many requests are already
* pending and the request will be retried later.
*/
- TerrainProvider.prototype.requestTileGeometry = function(x, y, level) {
+ TerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) {
throw new DeveloperError('This type should not be instantiated directly.');
};
@@ -154,11 +154,14 @@ define([
* @param {Number} x The X coordinate of the tile for which to request geometry.
* @param {Number} y The Y coordinate of the tile for which to request geometry.
* @param {Number} level The level of the tile for which to request geometry.
+ * @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
+ * or false if the request should be initiated regardless of the number of requests
+ * already in progress.
* @returns {Promise|TerrainData} A promise for the requested geometry. If this method
* returns undefined instead of a promise, it is an indication that too many requests are already
* pending and the request will be retried later.
*/
- VRTheWorldTerrainProvider.prototype.requestTileGeometry = function(x, y, level) {
+ VRTheWorldTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) {
if (!this.isReady()) {
throw new DeveloperError('requestTileGeometry must not be called before isReady returns true.');
}
@@ -171,9 +174,16 @@ define([
url = proxy.getURL(url);
}
- var promise = throttleRequestByServer(url, loadImage);
- if (typeof promise === 'undefined') {
- return undefined;
+ var promise;
+
+ throttleRequests = defaultValue(throttleRequests, true);
+ if (throttleRequests) {
+ promise = throttleRequestByServer(url, loadImage);
+ if (typeof promise === 'undefined') {
+ return undefined;
+ }
+ } else {
+ promise = loadImage(url);
}
var that = this;
@@ -0,0 +1,125 @@
+/*global define*/
+define([
+ '../Core/defaultValue',
+ '../Core/DeveloperError',
+ './TerrainState',
+ '../ThirdParty/when'
+ ], function(
+ defaultValue,
+ DeveloperError,
+ TerrainState,
+ when) {
+ "use strict";
+
+ /**
+ * 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.
+ *
+ * @exports 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} A promise that resolves to the provided list of positions when terrain the query has completed.
+ *
+ * @example
+ * // Query the terrain height of two Cartographic positions
+ * var terrainProvider = new CesiumTerrainProvider({
+ * url : 'http://cesium.agi.com/smallterrain'
+ * });
+ * var positions = [
+ * Cartographic.fromDegrees(86.925145, 27.988257),
+ * Cartographic.fromDegrees(87.0, 28.0)
+ * ];
+ * var promise = sampleTerrain(terrainProvider, 11, positions);
+ * when(promise, function(updatedPositions) {
+ * // positions[0].height and positions[1].height have been updated.
+ * // updatedPositions is just a reference to positions.
+ * });
+ */
+ var sampleTerrain = function(terrainProvider, level, positions) {
+ if (typeof terrainProvider === 'undefined') {
+ throw new DeveloperError('terrainProvider is required.');
+ }
+ if (typeof level === 'undefined') {
+ throw new DeveloperError('level is required.');
+ }
+ if (typeof positions === 'undefined') {
+ throw new DeveloperError('positions is required.');
+ }
+
+ var tilingScheme = terrainProvider.getTilingScheme();
+
+ 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, false);
+ var tilePromise = when(requestPromise, createInterpolateFunction(tileRequest), createMarkFailedFunction(tileRequest));
+ tilePromises.push(tilePromise);
+ }
+
+ return when.all(tilePromises, function() {
+ return positions;
+ });
+ };
+
+ function createInterpolateFunction(tileRequest) {
+ var tilePositions = tileRequest.positions;
+ var extent = tileRequest.tilingScheme.tileXYToExtent(tileRequest.x, tileRequest.y, tileRequest.level);
+ return function(terrainData) {
+ for (var i = 0; i < tilePositions.length; ++i) {
+ var position = tilePositions[i];
+ position.height = terrainData.interpolateHeight(extent, position.longitude, position.latitude);
+ }
+ };
+ }
+
+ function createMarkFailedFunction(tileRequest) {
+ var tilePositions = tileRequest.positions;
+ return function() {
+ for (var i = 0; i < tilePositions.length; ++i) {
+ var position = tilePositions[i];
+ position.height = undefined;
+ }
+ };
+ }
+
+ return sampleTerrain;
+});
Oops, something went wrong.

0 comments on commit f481633

Please sign in to comment.