From c0fc209fd1bd5d3d7de56b0be033987447bc7a99 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Thu, 15 Feb 2018 15:55:46 -0500 Subject: [PATCH 1/6] extrude polygons to terrain --- .../gallery/Extruded Polygon on Terrain.html | 66 ++++++ .../gallery/Extruded Polygon on Terrain.jpg | Bin 0 -> 24341 bytes Apps/Sandcastle/gallery/Picking.html | 2 +- CHANGES.md | 1 + Source/Core/ApproximateTerrainHeights.js | 203 ++++++++++++++++++ .../MinimumTerrainHeightProperty.js | 160 ++++++++++++++ .../VelocityOrientationProperty.js | 2 +- Source/DataSources/VelocityVectorProperty.js | 2 +- Source/Scene/GroundPrimitive.js | 130 ++--------- Specs/Core/ApproximateTerrainHeightsSpec.js | 70 ++++++ Specs/DataSources/DataSourceDisplaySpec.js | 8 +- Specs/DataSources/GeometryVisualizerSpec.js | 5 +- .../MinimumTerrainHeightPropertySpec.js | 134 ++++++++++++ Specs/Scene/GroundPrimitiveSpec.js | 5 +- 14 files changed, 663 insertions(+), 125 deletions(-) create mode 100644 Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html create mode 100644 Apps/Sandcastle/gallery/Extruded Polygon on Terrain.jpg create mode 100644 Source/Core/ApproximateTerrainHeights.js create mode 100644 Source/DataSources/MinimumTerrainHeightProperty.js create mode 100644 Specs/Core/ApproximateTerrainHeightsSpec.js create mode 100644 Specs/DataSources/MinimumTerrainHeightPropertySpec.js diff --git a/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html new file mode 100644 index 000000000000..1d0165d86025 --- /dev/null +++ b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html @@ -0,0 +1,66 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.jpg b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0cae73fff0af4dc943abdc150548f4e88ec84099 GIT binary patch literal 24341 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'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); + + var xyLevel = getTileXYLevel(rectangle); + + // Get the terrain min/max for that tile + var minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight; + var maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight; + if (defined(xyLevel)) { + var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y; + var 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.subtract(scratchDiagonalCartesianSW, scratchDiagonalCartesianNE, scratchCenterCartesian); + Cartesian3.add(scratchDiagonalCartesianNE, + Cartesian3.multiplyByScalar(scratchCenterCartesian, 0.5, scratchCenterCartesian), scratchCenterCartesian); + var surfacePosition = ellipsoid.scaleToGeodeticSurface(scratchCenterCartesian, scratchSurfaceCartesian); + if (defined(surfacePosition)) { + var 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.getInstanceBoundingSphere = 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); + + var xyLevel = getTileXYLevel(rectangle); + + // Get the terrain max for that tile + var maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight; + if (defined(xyLevel)) { + var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y; + var heights = ApproximateTerrainHeights._terrainHeights[key]; + if (defined(heights)) { + maxTerrainHeight = heights[1]; + } + } + + var 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 + var lastLevelX = 0, lastLevelY = 0; + var currentX = 0, currentY = 0; + var maxLevel = ApproximateTerrainHeights._terrainHeightsMaxLevel; + var i; + for(i = 0; i <= maxLevel; ++i) { + var failed = false; + for(var j = 0; j < 4; ++j) { + var 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; + + return ApproximateTerrainHeights; +}); diff --git a/Source/DataSources/MinimumTerrainHeightProperty.js b/Source/DataSources/MinimumTerrainHeightProperty.js new file mode 100644 index 000000000000..735bc6caaccc --- /dev/null +++ b/Source/DataSources/MinimumTerrainHeightProperty.js @@ -0,0 +1,160 @@ +define([ + '../Core/ApproximateTerrainHeights', + '../Core/Cartesian3', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/isArray', + '../Core/DeveloperError', + '../Core/Event', + '../Core/JulianDate', + '../Core/Rectangle', + './Property' +], function( + ApproximateTerrainHeights, + Cartesian3, + defaultValue, + defined, + defineProperties, + isArray, + DeveloperError, + Event, + JulianDate, + Rectangle, + Property) { + 'use strict'; + + /** + * A {@link Property} which evaluates to a {@link Number} based on the minimum height of terrain + * within the bounds of the provided positions. + * + * @alias MinimumTerrainHeightProperty + * @constructor + * + * @param {Property} [positions] A Property specifying the {@link PolygonHierarchy} or an array of {@link Cartesian3} positions. + * + * @example + * var hierarchy = new Cesium.ConstantProperty(polygonPositions); + * var redPolygon = viewer.entities.add({ + * polygon : { + * hierarchy : hierarchy, + * material : Cesium.Color.RED, + * height : 1800.0, + * extrudedHeight : new Cesium.MinimumTerrainHeightProperty(hierarchy) + * } + * }); + */ + function MinimumTerrainHeightProperty(positions) { + this._positions = undefined; + this._subscription = undefined; + this._definitionChanged = new Event(); + + this.positions = positions; + } + + defineProperties(MinimumTerrainHeightProperty.prototype, { + /** + * Gets a value indicating if this property is constant. + * @memberof MinimumTerrainHeightProperty.prototype + * + * @type {Boolean} + * @readonly + */ + isConstant : { + get : function() { + return Property.isConstant(this._positions); + } + }, + /** + * Gets the event that is raised whenever the definition of this property changes. + * @memberof MinimumTerrainHeightProperty.prototype + * + * @type {Event} + * @readonly + */ + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, + /** + * Gets or sets the positions property used to compute the value. + * @memberof MinimumTerrainHeightProperty.prototype + * + * @type {Property} + */ + positions : { + get : function() { + return this._positions; + }, + set : function(value) { + var oldValue = this._positions; + if (oldValue !== value) { + if (defined(oldValue)) { + this._subscription(); + } + + this._positions = value; + + if (defined(value)) { + this._subscription = value._definitionChanged.addEventListener(function() { + this._definitionChanged.raiseEvent(this); + }, this); + } + + this._definitionChanged.raiseEvent(this); + } + } + } + }); + + /** + * Gets the minimum terrain height based on the positions positions at the provided time. + * + * @param {JulianDate} time The time for which to retrieve the value. + * @returns {Number} The minimum terrain height + */ + MinimumTerrainHeightProperty.prototype.getValue = function(time) { + return this._getValue(time); + }; + + /** + * @private + */ + MinimumTerrainHeightProperty.prototype._getValue = function(time) { + //>>includeStart('debug', pragmas.debug); + if (!defined(time)) { + throw new DeveloperError('time is required'); + } + //>>includeEnd('debug'); + + var property = this._positions; + if (!defined(property)) { + return; + } + var positions = property.getValue(time); + if (!defined(positions)) { + return; + } + if (!isArray(positions)) { + positions = positions.positions; //positions is a PolygonHierarchy, just use the outer ring + } + var rectangle = Rectangle.fromCartesianArray(positions); + return ApproximateTerrainHeights.getApproximateTerrainHeights(rectangle).minimumTerrainHeight; + }; + + /** + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. + * + * @param {Property} [other] The other property. + * @returns {Boolean} true if left and right are equal, false otherwise. + */ + MinimumTerrainHeightProperty.prototype.equals = function(other) { + return this === other ||// + (other instanceof MinimumTerrainHeightProperty && + Property.equals(this._positions, other._positions)); + }; + + return MinimumTerrainHeightProperty; +}); diff --git a/Source/DataSources/VelocityOrientationProperty.js b/Source/DataSources/VelocityOrientationProperty.js index 46c11685fc6b..842777d63ff2 100644 --- a/Source/DataSources/VelocityOrientationProperty.js +++ b/Source/DataSources/VelocityOrientationProperty.js @@ -123,7 +123,7 @@ define([ /** * Gets the value of the property at the provided time. * - * @param {JulianDate} [time] The time for which to retrieve the value. + * @param {JulianDate} time The time for which to retrieve the value. * @param {Quaternion} [result] The object to store the value into, if omitted, a new instance is created and returned. * @returns {Quaternion} The modified result parameter or a new instance if the result parameter was not supplied. */ diff --git a/Source/DataSources/VelocityVectorProperty.js b/Source/DataSources/VelocityVectorProperty.js index 96a6e14f9535..c1ef5fb81eaa 100644 --- a/Source/DataSources/VelocityVectorProperty.js +++ b/Source/DataSources/VelocityVectorProperty.js @@ -133,7 +133,7 @@ define([ /** * Gets the value of the property at the provided time. * - * @param {JulianDate} [time] The time for which to retrieve the value. + * @param {JulianDate} time The time for which to retrieve the value. * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned. * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied. */ diff --git a/Source/Scene/GroundPrimitive.js b/Source/Scene/GroundPrimitive.js index c8a68f040822..40f94b847938 100644 --- a/Source/Scene/GroundPrimitive.js +++ b/Source/Scene/GroundPrimitive.js @@ -1,4 +1,5 @@ define([ + '../Core/ApproximateTerrainHeights', '../Core/BoundingSphere', '../Core/buildModuleUrl', '../Core/Cartesian2', @@ -22,6 +23,7 @@ define([ './ClassificationType', './SceneMode' ], function( + ApproximateTerrainHeights, BoundingSphere, buildModuleUrl, Cartesian2, @@ -207,8 +209,8 @@ define([ this._maxHeight = undefined; this._minHeight = undefined; - this._maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight; - this._minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight; + this._maxTerrainHeight = ApproximateTerrainHeights._defaultMaxTerrainHeight; + this._minTerrainHeight = ApproximateTerrainHeights._defaultMinTerrainHeight; this._boundingSpheresKeys = []; this._boundingSpheres = []; @@ -364,12 +366,6 @@ define([ */ GroundPrimitive.isSupported = ClassificationPrimitive.isSupported; - GroundPrimitive._defaultMaxTerrainHeight = 9000.0; - GroundPrimitive._defaultMinTerrainHeight = -100000.0; - - GroundPrimitive._terrainHeights = undefined; - GroundPrimitive._terrainHeightsMaxLevel = 6; - function getComputeMaximumHeightFunction(primitive) { return function(granularity, ellipsoid) { var r = ellipsoid.maximumRadius; @@ -389,9 +385,6 @@ define([ var scratchBVCartesian = new Cartesian3(); var scratchBVCartographic = new Cartographic(); var scratchBVRectangle = new Rectangle(); - var tilingScheme = new GeographicTilingScheme(); - var scratchCorners = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()]; - var scratchTileXY = new Cartesian2(); function getRectangle(frameState, geometry) { var ellipsoid = frameState.mapProjection.ellipsoid; @@ -438,110 +431,11 @@ define([ return rectangle; } - var scratchDiagonalCartesianNE = new Cartesian3(); - var scratchDiagonalCartesianSW = new Cartesian3(); - var scratchDiagonalCartographic = new Cartographic(); - var scratchCenterCartesian = new Cartesian3(); - var scratchSurfaceCartesian = new Cartesian3(); - - 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 - var lastLevelX = 0, lastLevelY = 0; - var currentX = 0, currentY = 0; - var maxLevel = GroundPrimitive._terrainHeightsMaxLevel; - var i; - for(i = 0; i <= maxLevel; ++i) { - var failed = false; - for(var j = 0; j < 4; ++j) { - var 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) - }; - } - function setMinMaxTerrainHeights(primitive, rectangle, ellipsoid) { - var xyLevel = getTileXYLevel(rectangle); - - // Get the terrain min/max for that tile - var minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight; - var maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight; - if (defined(xyLevel)) { - var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y; - var heights = GroundPrimitive._terrainHeights[key]; - if (defined(heights)) { - minTerrainHeight = heights[0]; - maxTerrainHeight = heights[1]; - } + var result = ApproximateTerrainHeights.getApproximateTerrainHeights(rectangle, ellipsoid); - // 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.subtract(scratchDiagonalCartesianSW, scratchDiagonalCartesianNE, scratchCenterCartesian); - Cartesian3.add(scratchDiagonalCartesianNE, - Cartesian3.multiplyByScalar(scratchCenterCartesian, 0.5, scratchCenterCartesian), scratchCenterCartesian); - var surfacePosition = ellipsoid.scaleToGeodeticSurface(scratchCenterCartesian, scratchSurfaceCartesian); - if (defined(surfacePosition)) { - var distance = Cartesian3.distance(scratchCenterCartesian, surfacePosition); - minTerrainHeight = Math.min(minTerrainHeight, -distance); - } else { - minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight; - } - } - - primitive._minTerrainHeight = Math.max(GroundPrimitive._defaultMinTerrainHeight, minTerrainHeight); - primitive._maxTerrainHeight = maxTerrainHeight; - } - - var scratchBoundingSphere = new BoundingSphere(); - function getInstanceBoundingSphere(rectangle, ellipsoid) { - var xyLevel = getTileXYLevel(rectangle); - - // Get the terrain max for that tile - var maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight; - if (defined(xyLevel)) { - var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y; - var heights = GroundPrimitive._terrainHeights[key]; - if (defined(heights)) { - maxTerrainHeight = heights[1]; - } - } - - var result = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0); - BoundingSphere.fromRectangle3D(rectangle, ellipsoid, maxTerrainHeight, scratchBoundingSphere); - - return BoundingSphere.union(result, scratchBoundingSphere, result); + primitive._minTerrainHeight = result.minimumTerrainHeight; + primitive._maxTerrainHeight = result.maximumTerrainHeight; } function createBoundingVolume(groundPrimitive, frameState, geometry) { @@ -663,10 +557,10 @@ define([ return initPromise; } - GroundPrimitive._initPromise = Resource.fetchJson(buildModuleUrl('Assets/approximateTerrainHeights.json')).then(function(json) { - GroundPrimitive._initialized = true; - GroundPrimitive._terrainHeights = json; - }); + GroundPrimitive._initPromise = ApproximateTerrainHeights.initialize() + .then(function() { + GroundPrimitive._initialized = true; + }); return GroundPrimitive._initPromise; }; @@ -727,7 +621,7 @@ define([ var id = instance.id; if (defined(id) && defined(instanceRectangle)) { - var boundingSphere = getInstanceBoundingSphere(instanceRectangle, ellipsoid); + var boundingSphere = ApproximateTerrainHeights.getInstanceBoundingSphere(instanceRectangle, ellipsoid); this._boundingSpheresKeys.push(id); this._boundingSpheres.push(boundingSphere); } diff --git a/Specs/Core/ApproximateTerrainHeightsSpec.js b/Specs/Core/ApproximateTerrainHeightsSpec.js new file mode 100644 index 000000000000..df85d432a348 --- /dev/null +++ b/Specs/Core/ApproximateTerrainHeightsSpec.js @@ -0,0 +1,70 @@ +defineSuite([ + 'Core/ApproximateTerrainHeights', + 'Core/Cartesian3', + 'Core/Math', + 'Core/Rectangle' +], function( + ApproximateTerrainHeights, + Cartesian3, + CesiumMath, + Rectangle) { + 'use strict'; + + beforeAll(function() { + return ApproximateTerrainHeights.initialize(); + }); + + afterAll(function() { + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; + }); + + it('initializes', function() { + return ApproximateTerrainHeights.initialize() + .then(function() { + expect(ApproximateTerrainHeights._terrainHeights).toBeDefined(); + }); + }); + + it('getApproximateTerrainHeights computes minimum and maximum terrain heights', function() { + var result = ApproximateTerrainHeights.getApproximateTerrainHeights(Rectangle.fromDegrees(-121.0, 10.0, -120.0, 11.0)); + expect(result.minimumTerrainHeight).toEqualEpsilon(-476.125711887558, CesiumMath.EPSILON10); + expect(result.maximumTerrainHeight).toEqualEpsilon(-28.53441619873047, CesiumMath.EPSILON10); + }); + + it('getApproximateTerrainHeights throws with no rectangle', function() { + expect(function() { + return ApproximateTerrainHeights.getApproximateTerrainHeights(); + }).toThrowDeveloperError(); + }); + + it('getApproximateTerrainHeights throws if ApproximateTerrainHeights was not initialized first', function() { + var heights = ApproximateTerrainHeights._terrainHeights; + ApproximateTerrainHeights._terrainHeights = undefined; + expect(function() { + return ApproximateTerrainHeights.getApproximateTerrainHeights(Rectangle.fromDegrees(-121.0, 10.0, -120.0, 11.0)); + }); + ApproximateTerrainHeights._terrainHeights = heights; + }); + + it('getInstanceBoundingSphere computes a bounding sphere', function() { + var result = ApproximateTerrainHeights.getInstanceBoundingSphere(Rectangle.fromDegrees(-121.0, 10.0, -120.0, 11.0)); + expect(result.center).toEqualEpsilon(new Cartesian3(-3183013.8480289434, -5403772.557261968, 1154581.5817616477), CesiumMath.EPSILON10); + expect(result.radius).toEqualEpsilon(77884.16539096291, CesiumMath.EPSILON10); + }); + + it('getInstanceBoundingSphere throws with no rectangle', function() { + expect(function() { + return ApproximateTerrainHeights.getInstanceBoundingSphere(); + }).toThrowDeveloperError(); + }); + + it('getInstanceBoundingSphere throws if ApproximateTerrainHeights was not initialized first', function() { + var heights = ApproximateTerrainHeights._terrainHeights; + ApproximateTerrainHeights._terrainHeights = undefined; + expect(function() { + return ApproximateTerrainHeights.getInstanceBoundingSphere(Rectangle.fromDegrees(-121.0, 10.0, -120.0, 11.0)); + }); + ApproximateTerrainHeights._terrainHeights = heights; + }); +}); diff --git a/Specs/DataSources/DataSourceDisplaySpec.js b/Specs/DataSources/DataSourceDisplaySpec.js index 2658683131a8..da18d098e531 100644 --- a/Specs/DataSources/DataSourceDisplaySpec.js +++ b/Specs/DataSources/DataSourceDisplaySpec.js @@ -1,5 +1,6 @@ defineSuite([ 'DataSources/DataSourceDisplay', + 'Core/ApproximateTerrainHeights', 'Core/BoundingSphere', 'Core/Cartesian3', 'Core/Iso8601', @@ -11,6 +12,7 @@ defineSuite([ 'Specs/MockDataSource' ], function( DataSourceDisplay, + ApproximateTerrainHeights, BoundingSphere, Cartesian3, Iso8601, @@ -38,7 +40,8 @@ defineSuite([ // Leave ground primitive uninitialized GroundPrimitive._initialized = false; GroundPrimitive._initPromise = undefined; - GroundPrimitive._terrainHeights = undefined; + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; }); afterEach(function() { @@ -358,7 +361,8 @@ defineSuite([ it('verify update returns false till terrain heights are initialized', function() { GroundPrimitive._initialized = false; GroundPrimitive._initPromise = undefined; - GroundPrimitive._terrainHeights = undefined; + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; var source1 = new MockDataSource(); var source2 = new MockDataSource(); diff --git a/Specs/DataSources/GeometryVisualizerSpec.js b/Specs/DataSources/GeometryVisualizerSpec.js index 837a189f3209..215f97cf9f19 100644 --- a/Specs/DataSources/GeometryVisualizerSpec.js +++ b/Specs/DataSources/GeometryVisualizerSpec.js @@ -1,5 +1,6 @@ defineSuite([ 'DataSources/GeometryVisualizer', + 'Core/ApproximateTerrainHeights', 'Core/BoundingSphere', 'Core/Cartesian3', 'Core/Color', @@ -30,6 +31,7 @@ defineSuite([ 'Specs/pollToPromise' ], function( GeometryVisualizer, + ApproximateTerrainHeights, BoundingSphere, Cartesian3, Color, @@ -75,7 +77,8 @@ defineSuite([ // Leave ground primitive uninitialized GroundPrimitive._initialized = false; GroundPrimitive._initPromise = undefined; - GroundPrimitive._terrainHeights = undefined; + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; }); it('Can create and destroy', function() { diff --git a/Specs/DataSources/MinimumTerrainHeightPropertySpec.js b/Specs/DataSources/MinimumTerrainHeightPropertySpec.js new file mode 100644 index 000000000000..60d1053dd57b --- /dev/null +++ b/Specs/DataSources/MinimumTerrainHeightPropertySpec.js @@ -0,0 +1,134 @@ +defineSuite([ + 'DataSources/MinimumTerrainHeightProperty', + 'Core/ApproximateTerrainHeights', + 'Core/Cartesian3', + 'Core/Event', + 'Core/ExtrapolationType', + 'Core/JulianDate', + 'Core/Math', + 'Core/PolygonHierarchy', + 'DataSources/CallbackProperty', + 'DataSources/ConstantProperty' +], function( + MinimumTerrainHeightProperty, + ApproximateTerrainHeights, + Cartesian3, + Event, + ExtrapolationType, + JulianDate, + CesiumMath, + PolygonHierarchy, + CallbackProperty, + ConstantProperty) { + 'use strict'; + + var time = JulianDate.now(); + + beforeAll(function() { + return ApproximateTerrainHeights.initialize(); + }); + + afterAll(function() { + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; + }); + + it('can default construct', function() { + var property = new MinimumTerrainHeightProperty(); + expect(property.isConstant).toBe(true); + expect(property.definitionChanged).toBeInstanceOf(Event); + expect(property.positions).toBeUndefined(); + expect(property.getValue(time)).toBeUndefined(); + }); + + it('can construct with arguments', function() { + var positions = new ConstantProperty(); + var property = new MinimumTerrainHeightProperty(positions); + expect(property.isConstant).toBe(true); + expect(property.definitionChanged).toBeInstanceOf(Event); + expect(property.positions).toBe(positions); + }); + + it('raises definitionChanged event when positions is set', function() { + var property = new MinimumTerrainHeightProperty(); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + var positions = new ConstantProperty(); + property.positions = positions; + expect(listener).toHaveBeenCalledWith(property); + }); + + it('subscribes and unsubscribes to position definitionChanged and propagates up', function() { + var positions = new ConstantProperty(); + var property = new MinimumTerrainHeightProperty(positions); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + //Position changing should raise out property change event + positions.definitionChanged.raiseEvent(positions); + expect(listener).toHaveBeenCalledWith(property); + + //Make sure it unsubscribes when value is changed + property.positions = undefined; + + listener.calls.reset(); + positions.definitionChanged.raiseEvent(positions); + expect(listener.calls.count()).toBe(0); + }); + + it('does not raise definitionChanged event when position is set to the same instance', function() { + var positions = new ConstantProperty(); + var property = new MinimumTerrainHeightProperty(positions); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + property.positions = positions; + expect(listener.calls.count()).toBe(0); + }); + + it('produces correct value', function() { + var positions = new ConstantProperty(new PolygonHierarchy(Cartesian3.fromDegreesArray([-120.0, 40.0, + -119.0, 40.0, + -119.0, 41.0, + -120.0, 41.0]))); + var property = new MinimumTerrainHeightProperty(positions); + + expect(property.getValue(time)).toEqualEpsilon(-382.8696126443784, CesiumMath.EPSILON10); + }); + + it('produces correct value for flat array of positions', function() { + var positions = new ConstantProperty(Cartesian3.fromDegreesArray([-120.0, 40.0, + -119.0, 40.0, + -119.0, 41.0, + -120.0, 41.0])); + var property = new MinimumTerrainHeightProperty(positions); + + expect(property.getValue(time)).toEqualEpsilon(-382.8696126443784, CesiumMath.EPSILON10); + }); + + it('equals works', function() { + var positions = new ConstantProperty(); + + var left = new MinimumTerrainHeightProperty(); + var right = new MinimumTerrainHeightProperty(); + + expect(left.equals(right)).toBe(true); + + left.positions = positions; + expect(left.equals(right)).toBe(false); + + right.positions = positions; + expect(left.equals(right)).toBe(true); + }); + + it('getValue throws without time', function() { + var property = new MinimumTerrainHeightProperty(); + expect(function() { + property.getValue(); + }).toThrowDeveloperError(); + }); +}); diff --git a/Specs/Scene/GroundPrimitiveSpec.js b/Specs/Scene/GroundPrimitiveSpec.js index a17aab5a121c..1d8c8b4d701e 100644 --- a/Specs/Scene/GroundPrimitiveSpec.js +++ b/Specs/Scene/GroundPrimitiveSpec.js @@ -1,5 +1,6 @@ defineSuite([ 'Scene/GroundPrimitive', + 'Core/ApproximateTerrainHeights', 'Core/Color', 'Core/ColorGeometryInstanceAttribute', 'Core/destroyObject', @@ -20,6 +21,7 @@ defineSuite([ 'Specs/pollToPromise' ], function( GroundPrimitive, + ApproximateTerrainHeights, Color, ColorGeometryInstanceAttribute, destroyObject, @@ -69,7 +71,8 @@ defineSuite([ // Leave ground primitive uninitialized GroundPrimitive._initialized = false; GroundPrimitive._initPromise = undefined; - GroundPrimitive._terrainHeights = undefined; + ApproximateTerrainHeights._initPromise = undefined; + ApproximateTerrainHeights._terrainHeights = undefined; }); function MockGlobePrimitive(primitive) { From 49839bdf49c58d90dde393107c46f98658a6dcbd Mon Sep 17 00:00:00 2001 From: hpinkos Date: Thu, 15 Feb 2018 18:10:08 -0500 Subject: [PATCH 2/6] cleanup --- Source/DataSources/MinimumTerrainHeightProperty.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Source/DataSources/MinimumTerrainHeightProperty.js b/Source/DataSources/MinimumTerrainHeightProperty.js index 735bc6caaccc..27f76b86cb56 100644 --- a/Source/DataSources/MinimumTerrainHeightProperty.js +++ b/Source/DataSources/MinimumTerrainHeightProperty.js @@ -115,13 +115,6 @@ define([ * @returns {Number} The minimum terrain height */ MinimumTerrainHeightProperty.prototype.getValue = function(time) { - return this._getValue(time); - }; - - /** - * @private - */ - MinimumTerrainHeightProperty.prototype._getValue = function(time) { //>>includeStart('debug', pragmas.debug); if (!defined(time)) { throw new DeveloperError('time is required'); From a10ad8d3d4118e93aba2d5a785452226d9cdb0aa Mon Sep 17 00:00:00 2001 From: hpinkos Date: Fri, 16 Feb 2018 11:16:40 -0500 Subject: [PATCH 3/6] cleanup --- .../gallery/Extruded Polygon on Terrain.html | 15 +++++++------- .../MinimumTerrainHeightProperty.js | 20 +++++++------------ 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html index 1d0165d86025..f8184c225210 100644 --- a/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html +++ b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html @@ -29,14 +29,15 @@ function startup(Cesium) { 'use strict'; //Sandcastle_Begin -var viewer = new Cesium.Viewer('cesiumContainer'); -viewer.scene.globe.depthTestAgainstTerrain = true; -var terrainProvider = new Cesium.CesiumTerrainProvider({ - url : 'https://assets.agi.com/stk-terrain/v1/tilesets/world/tiles', - requestWaterMask : true, - requestVertexNormals : true +var viewer = new Cesium.Viewer('cesiumContainer', { + baseLayerPicker: false, + terrainProvider: new Cesium.CesiumTerrainProvider({ + url : 'https://assets.agi.com/stk-terrain/v1/tilesets/world/tiles', + requestWaterMask : true, + requestVertexNormals : true + }) }); -viewer.terrainProvider = terrainProvider; +viewer.scene.globe.depthTestAgainstTerrain = true; var hierarchy = new Cesium.ConstantProperty(Cesium.Cartesian3.fromDegreesArray([-110.9365046146508,37.45609196222294, -110.9371577263633,37.46126425372983, -110.9455523711973,37.45766987108922, diff --git a/Source/DataSources/MinimumTerrainHeightProperty.js b/Source/DataSources/MinimumTerrainHeightProperty.js index 27f76b86cb56..73d30caa83dd 100644 --- a/Source/DataSources/MinimumTerrainHeightProperty.js +++ b/Source/DataSources/MinimumTerrainHeightProperty.js @@ -1,31 +1,27 @@ define([ '../Core/ApproximateTerrainHeights', - '../Core/Cartesian3', - '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/isArray', - '../Core/DeveloperError', + '../Core/Check', '../Core/Event', - '../Core/JulianDate', '../Core/Rectangle', './Property' ], function( ApproximateTerrainHeights, - Cartesian3, - defaultValue, defined, defineProperties, isArray, - DeveloperError, + Check, Event, - JulianDate, Rectangle, Property) { 'use strict'; + var scratchRectangle = new Rectangle(); + /** - * A {@link Property} which evaluates to a {@link Number} based on the minimum height of terrain + * A {@link Property} which evaluates to a Number based on the minimum height of terrain * within the bounds of the provided positions. * * @alias MinimumTerrainHeightProperty @@ -116,9 +112,7 @@ define([ */ MinimumTerrainHeightProperty.prototype.getValue = function(time) { //>>includeStart('debug', pragmas.debug); - if (!defined(time)) { - throw new DeveloperError('time is required'); - } + Check.defined('time', time); //>>includeEnd('debug'); var property = this._positions; @@ -132,7 +126,7 @@ define([ if (!isArray(positions)) { positions = positions.positions; //positions is a PolygonHierarchy, just use the outer ring } - var rectangle = Rectangle.fromCartesianArray(positions); + var rectangle = Rectangle.fromCartesianArray(positions, undefined, scratchRectangle); return ApproximateTerrainHeights.getApproximateTerrainHeights(rectangle).minimumTerrainHeight; }; From daa1e07fa474751cc023d23dc9c2bcdc339c29c1 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Tue, 27 Feb 2018 15:00:16 -0500 Subject: [PATCH 4/6] RelativeToTerrainHeightProperty --- .../gallery/Extruded Polygon on Terrain.html | 2 +- .../MinimumTerrainHeightProperty.js | 2 +- .../RelativeToTerrainHeightProperty.js | 201 ++++++++++++++++++ 3 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 Source/DataSources/RelativeToTerrainHeightProperty.js diff --git a/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html index f8184c225210..3339f697f101 100644 --- a/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html +++ b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html @@ -48,7 +48,7 @@ polygon : { hierarchy : hierarchy, material : Cesium.Color.RED, - height : 1800.0, + height : new Cesium.RelativeToTerrainHeightProperty(viewer.terrainProvider, hierarchy, 400.0), extrudedHeight : new Cesium.MinimumTerrainHeightProperty(hierarchy) } }); diff --git a/Source/DataSources/MinimumTerrainHeightProperty.js b/Source/DataSources/MinimumTerrainHeightProperty.js index 73d30caa83dd..8e065a78b041 100644 --- a/Source/DataSources/MinimumTerrainHeightProperty.js +++ b/Source/DataSources/MinimumTerrainHeightProperty.js @@ -105,7 +105,7 @@ define([ }); /** - * Gets the minimum terrain height based on the positions positions at the provided time. + * Gets the minimum terrain height based on the positions at the provided time. * * @param {JulianDate} time The time for which to retrieve the value. * @returns {Number} The minimum terrain height diff --git a/Source/DataSources/RelativeToTerrainHeightProperty.js b/Source/DataSources/RelativeToTerrainHeightProperty.js new file mode 100644 index 000000000000..0812fb3729ff --- /dev/null +++ b/Source/DataSources/RelativeToTerrainHeightProperty.js @@ -0,0 +1,201 @@ +define([ + '../Core/ApproximateTerrainHeights', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/isArray', + '../Core/Cartesian3', + '../Core/Cartographic', + '../Core/Check', + '../Core/Event', + '../Core/Iso8601', + '../Core/Rectangle', + '../Core/RuntimeError', + '../Core/sampleTerrainMostDetailed', + './createPropertyDescriptor', + './Property' +], function( + ApproximateTerrainHeights, + defaultValue, + defined, + defineProperties, + isArray, + Cartesian3, + Cartographic, + Check, + Event, + Iso8601, + Rectangle, + RuntimeError, + sampleTerrainMostDetailed, + createPropertyDescriptor, + Property) { + 'use strict'; + + /** + * A {@link Property} which evaluates to a Number based on the height of terrain + * within the bounds of the provided positions. + * + * @alias RelativeToTerrainHeightProperty + * @constructor + * + * @param {TerrainProvider} terrainProvider The terrain provider used on the globe + * @param {Property} [positions] A Property specifying the {@link PolygonHierarchy} or an array of {@link Cartesian3} positions. + * @param {Property} [heightRelativeToTerrain] A Property specifying the numeric height value relative to terrain + * + * @example + * var hierarchy = new Cesium.ConstantProperty(polygonPositions); + * var redPolygon = viewer.entities.add({ + * polygon : { + * hierarchy : hierarchy, + * material : Cesium.Color.RED, + * height : new Cesium.RelativeToTerrainHeightProperty(hierarchy, 11.0) + * } + * }); + */ + function RelativeToTerrainHeightProperty(terrainProvider, positions, heightRelativeToTerrain) { + //>>includeStart('debug', pragmas.debug); + Check.defined('terrainProvider', terrainProvider); + //>>includeEnd('debug'); + + this._positions = undefined; + this._subscription = undefined; + this._heightRelativeToTerrain = undefined; + this._heightRelativeToTerrainSubscription = undefined; + this._definitionChanged = new Event(); + + this._terrainProvider = terrainProvider; + this._cartographicPosition = new Cartographic(); + this._terrainHeight = 0; + + this.positions = positions; + this.heightRelativeToTerrain = heightRelativeToTerrain; + } + + defineProperties(RelativeToTerrainHeightProperty.prototype, { + /** + * Gets a value indicating if this property is constant. + * @memberof RelativeToTerrainHeightProperty.prototype + * + * @type {Boolean} + * @readonly + */ + isConstant : { + get : function() { + return Property.isConstant(this._positions); + } + }, + /** + * Gets the event that is raised whenever the definition of this property changes. + * @memberof RelativeToTerrainHeightProperty.prototype + * + * @type {Event} + * @readonly + */ + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, + /** + * Gets or sets the positions property used to compute the value. + * @memberof RelativeToTerrainHeightProperty.prototype + * + * @type {Property} + */ + positions : { + get : function() { + return this._positions; + }, + set : function(value) { + var oldValue = this._positions; + if (oldValue !== value) { + if (defined(oldValue)) { + this._subscription(); + } + + this._positions = value; + + if (defined(value)) { + if (!value.isConstant) { + throw new RuntimeError('positions must be a constant property'); + } + this._subscription = value._definitionChanged.addEventListener(function() { + this._fetchTerrainHeight(); + this._definitionChanged.raiseEvent(this); + }, this); + } + + this._fetchTerrainHeight(); + this._definitionChanged.raiseEvent(this); + } + } + }, + heightRelativeToTerrain : createPropertyDescriptor('heightRelativeToTerrain') + }); + + var centroidScratch = new Cartesian3(); + function computeCentroid(positions) { + var centroid = Cartesian3.clone(Cartesian3.ZERO, centroidScratch); + var length = positions.length; + for (var i = 0; i < length; i++) { + centroid = Cartesian3.add(positions[i], centroid, centroid); + } + return Cartesian3.multiplyByScalar(centroid, 1/length, centroid); + } + + /** + * @private + */ + RelativeToTerrainHeightProperty.prototype._fetchTerrainHeight = function() { + this._terrainHeight = 0; + var property = this._positions; + if (!defined(property)) { + return; + } + var positions = property.getValue(Iso8601.MINIMUM_VALUE); + if (!defined(positions)) { + return; + } + if (!isArray(positions)) { + positions = positions.positions; //positions is a PolygonHierarchy, just use the outer ring + } + var centroid = computeCentroid(positions); + var carto = Cartographic.fromCartesian(centroid, undefined, this._cartographicPosition); + carto.height = 0.0; + var that = this; + sampleTerrainMostDetailed(this._terrainProvider, [carto]) + .then(function(results) { + if (that._positions !== property) { + return; + } + that._terrainHeight = defaultValue(results[0].height, 0); + that._definitionChanged.raiseEvent(that); + }); + }; + + /** + * Gets the height relative to the terrain based on the positions. + * + * @returns {Number} The height relative to terrain + */ + RelativeToTerrainHeightProperty.prototype.getValue = function(time) { + return this._terrainHeight + this.heightRelativeToTerrain.getValue(time); + }; + + /** + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. + * + * @param {Property} [other] The other property. + * @returns {Boolean} true if left and right are equal, false otherwise. + */ + RelativeToTerrainHeightProperty.prototype.equals = function(other) { + return this === other ||// + (other instanceof RelativeToTerrainHeightProperty && + Property.equals(this._positions, other._positions) && + Property.equals(this.heightRelativeToTerrain, other.heightRelativeToTerrain)); + }; + + return RelativeToTerrainHeightProperty; +}); From 0607c276ece10bff9115fc903591d531ca058a86 Mon Sep 17 00:00:00 2001 From: hpinkos Date: Wed, 28 Feb 2018 17:09:56 -0500 Subject: [PATCH 5/6] centroid property [ci skip] --- .../gallery/Extruded Polygon on Terrain.html | 11 +- .../DataSources/CentroidPositionProperty.js | 151 ++++++++++++++++++ .../MinimumTerrainHeightProperty.js | 45 ++---- Source/DataSources/PositionProperty.js | 2 +- .../RelativeToTerrainHeightProperty.js | 54 +++---- Source/Widgets/Viewer/Viewer.js | 2 +- .../MinimumTerrainHeightPropertySpec.js | 10 +- 7 files changed, 190 insertions(+), 85 deletions(-) create mode 100644 Source/DataSources/CentroidPositionProperty.js diff --git a/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html index 3339f697f101..0daead9e19d1 100644 --- a/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html +++ b/Apps/Sandcastle/gallery/Extruded Polygon on Terrain.html @@ -38,18 +38,19 @@ }) }); viewer.scene.globe.depthTestAgainstTerrain = true; -var hierarchy = new Cesium.ConstantProperty(Cesium.Cartesian3.fromDegreesArray([-110.9365046146508,37.45609196222294, +var positions = Cesium.Cartesian3.fromDegreesArray([-110.9365046146508,37.45609196222294, -110.9371577263633,37.46126425372983, -110.9455523711973,37.45766987108922, -110.9433924350152,37.45268714057247, - -110.9365046146508,37.45609196222294])); + -110.9365046146508,37.45609196222294]); +var centroid = new Cesium.CentroidPositionProperty(positions); var redPolygon = viewer.entities.add({ polygon : { - hierarchy : hierarchy, + hierarchy : positions, material : Cesium.Color.RED, - height : new Cesium.RelativeToTerrainHeightProperty(viewer.terrainProvider, hierarchy, 400.0), - extrudedHeight : new Cesium.MinimumTerrainHeightProperty(hierarchy) + height : new Cesium.RelativeToTerrainHeightProperty(viewer.terrainProvider, centroid, 400.0), + extrudedHeight : new Cesium.MinimumTerrainHeightProperty(positions) } }); diff --git a/Source/DataSources/CentroidPositionProperty.js b/Source/DataSources/CentroidPositionProperty.js new file mode 100644 index 000000000000..6eac6f063818 --- /dev/null +++ b/Source/DataSources/CentroidPositionProperty.js @@ -0,0 +1,151 @@ +define([ + '../Core/Cartesian3', + '../Core/Check', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/DeveloperError', + '../Core/Event', + '../Core/ReferenceFrame', + './createPropertyDescriptor', + './PositionProperty', + './Property' +], function( + Cartesian3, + Check, + defaultValue, + defined, + defineProperties, + DeveloperError, + Event, + ReferenceFrame, + createPropertyDescriptor, + PositionProperty, + Property) { + 'use strict'; + + function computeCentroid(positions, result) { + var centroid = Cartesian3.clone(Cartesian3.ZERO, result); + var length = positions.length; + for (var i = 0; i < length; i++) { + centroid = Cartesian3.add(positions[i], centroid, centroid); + } + return Cartesian3.multiplyByScalar(centroid, 1 / length, centroid); + } + + /** + * A {@link PositionProperty} whose value is the centroid of the given list of positions + * + * @alias CentroidPositionProperty + * @constructor + * + * @param {Property} [positions] The property value that resolves to an array of Cartesian3 positions. + */ + function CentroidPositionProperty(positions) { + this._definitionChanged = new Event(); + this._positions = undefined; + this._positionsSubscription = undefined; + this._referenceFrame = ReferenceFrame.FIXED; + + this.positions = positions; + } + + defineProperties(CentroidPositionProperty.prototype, { + /** + * Gets a value indicating if this property is constant. A property is considered + * constant if getValue always returns the same result for the current definition. + * @memberof CentroidPositionProperty.prototype + * + * @type {Boolean} + * @readonly + */ + isConstant : { + get : function() { + return Property.isConstant(this._positions); + } + }, + /** + * Gets the event that is raised whenever the definition of this property changes. + * The definition is considered to have changed if a call to getValue would return + * a different result for the same time. + * @memberof CentroidPositionProperty.prototype + * + * @type {Event} + * @readonly + */ + definitionChanged : { + get : function() { + return this._definitionChanged; + } + }, + /** + * Gets the reference frame in which the position is defined. + * @memberof CentroidPositionProperty.prototype + * @type {ReferenceFrame} + * @default ReferenceFrame.FIXED; + */ + referenceFrame : { + get : function() { + return this._referenceFrame; + } + }, + /** + * Gets or sets the positions property used to compute the value. + * @memberof CentroidPositionProperty.prototype + * + * @type {Property} + */ + positions : createPropertyDescriptor('positions') + }); + + /** + * Gets the value of the property at the provided time in the fixed frame. + * + * @param {JulianDate} time The time for which to retrieve the value. + * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned. + * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied. + */ + CentroidPositionProperty.prototype.getValue = function(time, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('time', time); + //>>includeEnd('debug'); + + var positions = Property.getValueOrUndefined(this._positions, time); + if (!defined(positions)) { + return; + } + return computeCentroid(positions, result); + }; + + /** + * Gets the value of the property at the provided time and in the provided reference frame. + * + * @param {JulianDate} time The time for which to retrieve the value. + * @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result. + * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned. + * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied. + */ + CentroidPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) { + //>>includeStart('debug', pragmas.debug); + Check.defined('time', time); + Check.defined('referenceFrame', referenceFrame); + //>>includeEnd('debug'); + var value = this.getValue(time, result); + return PositionProperty.convertToReferenceFrame(time, value, this._referenceFrame, referenceFrame, result); + }; + + /** + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. + * + * @param {Property} [other] The other property. + * @returns {Boolean} true if left and right are equal, false otherwise. + */ + CentroidPositionProperty.prototype.equals = function(other) { + return this === other || + (other instanceof CentroidPositionProperty && + Cartesian3.equals(this._positions, other._positions)); + }; + + return CentroidPositionProperty; +}); diff --git a/Source/DataSources/MinimumTerrainHeightProperty.js b/Source/DataSources/MinimumTerrainHeightProperty.js index 8e065a78b041..bc17d8b61562 100644 --- a/Source/DataSources/MinimumTerrainHeightProperty.js +++ b/Source/DataSources/MinimumTerrainHeightProperty.js @@ -6,6 +6,7 @@ define([ '../Core/Check', '../Core/Event', '../Core/Rectangle', + './createPropertyDescriptor', './Property' ], function( ApproximateTerrainHeights, @@ -15,6 +16,7 @@ define([ Check, Event, Rectangle, + createPropertyDescriptor, Property) { 'use strict'; @@ -27,22 +29,22 @@ define([ * @alias MinimumTerrainHeightProperty * @constructor * - * @param {Property} [positions] A Property specifying the {@link PolygonHierarchy} or an array of {@link Cartesian3} positions. + * @param {Property} [positions] A Property specifying an array of {@link Cartesian3} positions. * * @example - * var hierarchy = new Cesium.ConstantProperty(polygonPositions); + * var polygonPositions = new Cesium.ConstantProperty(polygonPositions); * var redPolygon = viewer.entities.add({ * polygon : { - * hierarchy : hierarchy, + * hierarchy : polygonPositions, * material : Cesium.Color.RED, * height : 1800.0, - * extrudedHeight : new Cesium.MinimumTerrainHeightProperty(hierarchy) + * extrudedHeight : new Cesium.MinimumTerrainHeightProperty(polygonPositions) * } * }); */ function MinimumTerrainHeightProperty(positions) { this._positions = undefined; - this._subscription = undefined; + this._positionsSubscription = undefined; this._definitionChanged = new Event(); this.positions = positions; @@ -79,29 +81,7 @@ define([ * * @type {Property} */ - positions : { - get : function() { - return this._positions; - }, - set : function(value) { - var oldValue = this._positions; - if (oldValue !== value) { - if (defined(oldValue)) { - this._subscription(); - } - - this._positions = value; - - if (defined(value)) { - this._subscription = value._definitionChanged.addEventListener(function() { - this._definitionChanged.raiseEvent(this); - }, this); - } - - this._definitionChanged.raiseEvent(this); - } - } - } + positions : createPropertyDescriptor('positions') }); /** @@ -115,17 +95,10 @@ define([ Check.defined('time', time); //>>includeEnd('debug'); - var property = this._positions; - if (!defined(property)) { - return; - } - var positions = property.getValue(time); + var positions = Property.getValueOrUndefined(this._positions, time); if (!defined(positions)) { return; } - if (!isArray(positions)) { - positions = positions.positions; //positions is a PolygonHierarchy, just use the outer ring - } var rectangle = Rectangle.fromCartesianArray(positions, undefined, scratchRectangle); return ApproximateTerrainHeights.getApproximateTerrainHeights(rectangle).minimumTerrainHeight; }; diff --git a/Source/DataSources/PositionProperty.js b/Source/DataSources/PositionProperty.js index af5b00665e19..bffeacf488c0 100644 --- a/Source/DataSources/PositionProperty.js +++ b/Source/DataSources/PositionProperty.js @@ -105,7 +105,7 @@ define([ */ PositionProperty.convertToReferenceFrame = function(time, value, inputFrame, outputFrame, result) { if (!defined(value)) { - return value; + return; } if (!defined(result)){ result = new Cartesian3(); diff --git a/Source/DataSources/RelativeToTerrainHeightProperty.js b/Source/DataSources/RelativeToTerrainHeightProperty.js index 0812fb3729ff..7b801c4d50c3 100644 --- a/Source/DataSources/RelativeToTerrainHeightProperty.js +++ b/Source/DataSources/RelativeToTerrainHeightProperty.js @@ -40,7 +40,7 @@ define([ * @constructor * * @param {TerrainProvider} terrainProvider The terrain provider used on the globe - * @param {Property} [positions] A Property specifying the {@link PolygonHierarchy} or an array of {@link Cartesian3} positions. + * @param {PositionProperty} position A Property specifying the position the height should be relative to. * @param {Property} [heightRelativeToTerrain] A Property specifying the numeric height value relative to terrain * * @example @@ -53,12 +53,12 @@ define([ * } * }); */ - function RelativeToTerrainHeightProperty(terrainProvider, positions, heightRelativeToTerrain) { + function RelativeToTerrainHeightProperty(terrainProvider, position, heightRelativeToTerrain) { //>>includeStart('debug', pragmas.debug); Check.defined('terrainProvider', terrainProvider); //>>includeEnd('debug'); - this._positions = undefined; + this._position = undefined; this._subscription = undefined; this._heightRelativeToTerrain = undefined; this._heightRelativeToTerrainSubscription = undefined; @@ -68,7 +68,7 @@ define([ this._cartographicPosition = new Cartographic(); this._terrainHeight = 0; - this.positions = positions; + this.position = position; this.heightRelativeToTerrain = heightRelativeToTerrain; } @@ -98,14 +98,14 @@ define([ } }, /** - * Gets or sets the positions property used to compute the value. + * Gets or sets the position property used to compute the value. * @memberof RelativeToTerrainHeightProperty.prototype * - * @type {Property} + * @type {PositionProperty} */ - positions : { + position : { get : function() { - return this._positions; + return this._position; }, set : function(value) { var oldValue = this._positions; @@ -114,59 +114,41 @@ define([ this._subscription(); } - this._positions = value; + this._position = value; if (defined(value)) { if (!value.isConstant) { - throw new RuntimeError('positions must be a constant property'); + throw new RuntimeError('position must be a constant property'); } this._subscription = value._definitionChanged.addEventListener(function() { this._fetchTerrainHeight(); - this._definitionChanged.raiseEvent(this); }, this); } this._fetchTerrainHeight(); - this._definitionChanged.raiseEvent(this); } } }, heightRelativeToTerrain : createPropertyDescriptor('heightRelativeToTerrain') }); - var centroidScratch = new Cartesian3(); - function computeCentroid(positions) { - var centroid = Cartesian3.clone(Cartesian3.ZERO, centroidScratch); - var length = positions.length; - for (var i = 0; i < length; i++) { - centroid = Cartesian3.add(positions[i], centroid, centroid); - } - return Cartesian3.multiplyByScalar(centroid, 1/length, centroid); - } - /** * @private */ RelativeToTerrainHeightProperty.prototype._fetchTerrainHeight = function() { this._terrainHeight = 0; - var property = this._positions; - if (!defined(property)) { + var property = this._position; + var position = Property.getValueOrUndefined(property, Iso8601.MINIMUM_VALUE); + if (!defined(position)) { return; } - var positions = property.getValue(Iso8601.MINIMUM_VALUE); - if (!defined(positions)) { - return; - } - if (!isArray(positions)) { - positions = positions.positions; //positions is a PolygonHierarchy, just use the outer ring - } - var centroid = computeCentroid(positions); - var carto = Cartographic.fromCartesian(centroid, undefined, this._cartographicPosition); + + var carto = Cartographic.fromCartesian(position, undefined, this._cartographicPosition); carto.height = 0.0; var that = this; sampleTerrainMostDetailed(this._terrainProvider, [carto]) .then(function(results) { - if (that._positions !== property) { + if (that._position !== property || !Cartesian3.equals(position, Property.getValueOrUndefined(that._position, Iso8601.MINIMUM_VALUE))) { return; } that._terrainHeight = defaultValue(results[0].height, 0); @@ -180,7 +162,7 @@ define([ * @returns {Number} The height relative to terrain */ RelativeToTerrainHeightProperty.prototype.getValue = function(time) { - return this._terrainHeight + this.heightRelativeToTerrain.getValue(time); + return this._terrainHeight + Property.getValueOrDefault(this.heightRelativeToTerrain, time, 0); }; /** @@ -193,7 +175,7 @@ define([ RelativeToTerrainHeightProperty.prototype.equals = function(other) { return this === other ||// (other instanceof RelativeToTerrainHeightProperty && - Property.equals(this._positions, other._positions) && + Property.equals(this._position, other._position) && Property.equals(this.heightRelativeToTerrain, other.heightRelativeToTerrain)); }; diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js index 43586bdb1484..93bf1d600553 100644 --- a/Source/Widgets/Viewer/Viewer.js +++ b/Source/Widgets/Viewer/Viewer.js @@ -555,7 +555,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to } // These need to be set after the BaseLayerPicker is created in order to take effect - if (defined(options.imageryProvider)) { + if (defined(options.imageryProvider) && options.imageryProvider !== false) { if (createBaseLayerPicker) { baseLayerPicker.viewModel.selectedImagery = undefined; } diff --git a/Specs/DataSources/MinimumTerrainHeightPropertySpec.js b/Specs/DataSources/MinimumTerrainHeightPropertySpec.js index 60d1053dd57b..3ab92eab5d16 100644 --- a/Specs/DataSources/MinimumTerrainHeightPropertySpec.js +++ b/Specs/DataSources/MinimumTerrainHeightPropertySpec.js @@ -6,7 +6,6 @@ defineSuite([ 'Core/ExtrapolationType', 'Core/JulianDate', 'Core/Math', - 'Core/PolygonHierarchy', 'DataSources/CallbackProperty', 'DataSources/ConstantProperty' ], function( @@ -17,7 +16,6 @@ defineSuite([ ExtrapolationType, JulianDate, CesiumMath, - PolygonHierarchy, CallbackProperty, ConstantProperty) { 'use strict'; @@ -57,7 +55,7 @@ defineSuite([ var positions = new ConstantProperty(); property.positions = positions; - expect(listener).toHaveBeenCalledWith(property); + expect(listener).toHaveBeenCalledWith(property, 'positions', positions, undefined); }); it('subscribes and unsubscribes to position definitionChanged and propagates up', function() { @@ -69,7 +67,7 @@ defineSuite([ //Position changing should raise out property change event positions.definitionChanged.raiseEvent(positions); - expect(listener).toHaveBeenCalledWith(property); + expect(listener).toHaveBeenCalledWith(property, 'positions', positions, positions); //Make sure it unsubscribes when value is changed property.positions = undefined; @@ -91,10 +89,10 @@ defineSuite([ }); it('produces correct value', function() { - var positions = new ConstantProperty(new PolygonHierarchy(Cartesian3.fromDegreesArray([-120.0, 40.0, + var positions = new ConstantProperty(Cartesian3.fromDegreesArray([-120.0, 40.0, -119.0, 40.0, -119.0, 41.0, - -120.0, 41.0]))); + -120.0, 41.0])); var property = new MinimumTerrainHeightProperty(positions); expect(property.getValue(time)).toEqualEpsilon(-382.8696126443784, CesiumMath.EPSILON10); From 62ca17c0b55926a9bab59e10a59f491c0bace19d Mon Sep 17 00:00:00 2001 From: hpinkos Date: Wed, 28 Feb 2018 17:50:08 -0500 Subject: [PATCH 6/6] broken specs [ci skip] --- .../RelativeToTerrainHeightProperty.js | 21 ++- .../CentroidPositionPropertySpec.js | 99 +++++++++++ .../MinimumTerrainHeightPropertySpec.js | 10 -- .../RelativeToTerrainHeightPropertySpec.js | 166 ++++++++++++++++++ 4 files changed, 280 insertions(+), 16 deletions(-) create mode 100644 Specs/DataSources/CentroidPositionPropertySpec.js create mode 100644 Specs/DataSources/RelativeToTerrainHeightPropertySpec.js diff --git a/Source/DataSources/RelativeToTerrainHeightProperty.js b/Source/DataSources/RelativeToTerrainHeightProperty.js index 7b801c4d50c3..e1b538079602 100644 --- a/Source/DataSources/RelativeToTerrainHeightProperty.js +++ b/Source/DataSources/RelativeToTerrainHeightProperty.js @@ -46,10 +46,10 @@ define([ * @example * var hierarchy = new Cesium.ConstantProperty(polygonPositions); * var redPolygon = viewer.entities.add({ - * polygon : { + * ellipse : { * hierarchy : hierarchy, * material : Cesium.Color.RED, - * height : new Cesium.RelativeToTerrainHeightProperty(hierarchy, 11.0) + * height : new Cesium.RelativeToTerrainHeightProperty(viewer.terrainProvider, positions, 11.0) * } * }); */ @@ -67,6 +67,7 @@ define([ this._terrainProvider = terrainProvider; this._cartographicPosition = new Cartographic(); this._terrainHeight = 0; + this._pending = false; this.position = position; this.heightRelativeToTerrain = heightRelativeToTerrain; @@ -117,9 +118,6 @@ define([ this._position = value; if (defined(value)) { - if (!value.isConstant) { - throw new RuntimeError('position must be a constant property'); - } this._subscription = value._definitionChanged.addEventListener(function() { this._fetchTerrainHeight(); }, this); @@ -136,6 +134,9 @@ define([ * @private */ RelativeToTerrainHeightProperty.prototype._fetchTerrainHeight = function() { + if (this._pending) { + return; + } this._terrainHeight = 0; var property = this._position; var position = Property.getValueOrUndefined(property, Iso8601.MINIMUM_VALUE); @@ -146,13 +147,17 @@ define([ var carto = Cartographic.fromCartesian(position, undefined, this._cartographicPosition); carto.height = 0.0; var that = this; - sampleTerrainMostDetailed(this._terrainProvider, [carto]) + this._pending = true; + RelativeToTerrainHeightProperty._sampleTerrainMostDetailed(this._terrainProvider, [carto]) .then(function(results) { if (that._position !== property || !Cartesian3.equals(position, Property.getValueOrUndefined(that._position, Iso8601.MINIMUM_VALUE))) { return; } that._terrainHeight = defaultValue(results[0].height, 0); that._definitionChanged.raiseEvent(that); + }) + .always(function() { + that._pending = false; }); }; @@ -175,9 +180,13 @@ define([ RelativeToTerrainHeightProperty.prototype.equals = function(other) { return this === other ||// (other instanceof RelativeToTerrainHeightProperty && + this._terrainProvider === other._terrainProvider && Property.equals(this._position, other._position) && Property.equals(this.heightRelativeToTerrain, other.heightRelativeToTerrain)); }; + //for specs + RelativeToTerrainHeightProperty._sampleTerrainMostDetailed = sampleTerrainMostDetailed; + return RelativeToTerrainHeightProperty; }); diff --git a/Specs/DataSources/CentroidPositionPropertySpec.js b/Specs/DataSources/CentroidPositionPropertySpec.js new file mode 100644 index 000000000000..f8623d9bb010 --- /dev/null +++ b/Specs/DataSources/CentroidPositionPropertySpec.js @@ -0,0 +1,99 @@ +defineSuite([ + 'DataSources/CentroidPositionProperty', + 'Core/Cartesian3', + 'Core/JulianDate', + 'Core/ReferenceFrame', + 'DataSources/ConstantProperty', + 'DataSources/PositionProperty' +], function( + CentroidPositionProperty, + Cartesian3, + JulianDate, + ReferenceFrame, + ConstantProperty, + PositionProperty) { + 'use strict'; + + var time = JulianDate.now(); + + it('Constructor sets expected defaults', function() { + var positions = new ConstantProperty(Cartesian3.fromDegreesArray([0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0])); + var property = new CentroidPositionProperty(positions); + expect(property.referenceFrame).toBe(ReferenceFrame.FIXED); + expect(property.positions).toBe(positions); + }); + + it('getValue works without a result parameter', function() { + var positions = new ConstantProperty(new Cartesian3(0.0, 0.0, 0.0), + new Cartesian3(1.0, 0.0, 0.0), + new Cartesian3(1.0, 1.0, 0.0), + new Cartesian3(0.0, 1.0, 0.0)); + var property = new CentroidPositionProperty(positions); + + var result = property.getValue(time); + expect(result).toEqual(new Cartesian3(0.5, 0.5, 0.0)); + }); + + it('getValue works with a result parameter', function() { + var positions = new ConstantProperty([new Cartesian3(0.0, 0.0, 0.0), + new Cartesian3(1.0, 0.0, 0.0), + new Cartesian3(1.0, 1.0, 0.0), + new Cartesian3(0.0, 1.0, 0.0)]); + var property = new CentroidPositionProperty(positions); + + var expected = new Cartesian3(); + var result = property.getValue(time, expected); + expect(result).toBe(expected); + expect(expected).toEqual(new Cartesian3(0.5, 0.5, 0.0)); + }); + + it('getValueInReferenceFrame works without a result parameter', function() { + var positions = new ConstantProperty([new Cartesian3(0.0, 0.0, 0.0), + new Cartesian3(1.0, 0.0, 0.0), + new Cartesian3(1.0, 1.0, 0.0), + new Cartesian3(0.0, 1.0, 0.0)]); + var property = new CentroidPositionProperty(positions); + + var result = property.getValueInReferenceFrame(time, ReferenceFrame.INERTIAL); + expect(result).toEqual(PositionProperty.convertToReferenceFrame(time, new Cartesian3(0.5, 0.5, 0.0), ReferenceFrame.FIXED, ReferenceFrame.INERTIAL)); + }); + + it('getValueInReferenceFrame works with a result parameter', function() { + var positions = new ConstantProperty([new Cartesian3(0.0, 0.0, 0.0), + new Cartesian3(1.0, 0.0, 0.0), + new Cartesian3(1.0, 1.0, 0.0), + new Cartesian3(0.0, 1.0, 0.0)]); + var property = new CentroidPositionProperty(positions); + + var expected = new Cartesian3(); + var result = property.getValueInReferenceFrame(time, ReferenceFrame.FIXED, expected); + expect(result).toBe(expected); + expect(expected).toEqual(PositionProperty.convertToReferenceFrame(time, new Cartesian3(0.5, 0.5, 0.0), ReferenceFrame.INERTIAL, ReferenceFrame.FIXED)); + }); + + it('equals works', function() { + var left = new CentroidPositionProperty([new Cartesian3(1, 2, 3)]); + var right = new CentroidPositionProperty([new Cartesian3(1, 2, 3)]); + expect(left.equals(right)).toEqual(true); + + right = new CentroidPositionProperty([new Cartesian3(1, 2, 4)]); + expect(left.equals(right)).toEqual(false); + }); + + it('getValue throws without time parameter', function() { + var property = new CentroidPositionProperty([new Cartesian3(1, 2, 3)]); + expect(function() { + property.getValue(undefined); + }).toThrowDeveloperError(); + }); + + it('getValueInReferenceFrame throws with no referenceFrame parameter', function() { + var property = new CentroidPositionProperty([new Cartesian3(1, 2, 3)]); + expect(function() { + property.getValueInReferenceFrame(time, undefined); + }).toThrowDeveloperError(); + }); +}); diff --git a/Specs/DataSources/MinimumTerrainHeightPropertySpec.js b/Specs/DataSources/MinimumTerrainHeightPropertySpec.js index 3ab92eab5d16..2deb902ef04d 100644 --- a/Specs/DataSources/MinimumTerrainHeightPropertySpec.js +++ b/Specs/DataSources/MinimumTerrainHeightPropertySpec.js @@ -98,16 +98,6 @@ defineSuite([ expect(property.getValue(time)).toEqualEpsilon(-382.8696126443784, CesiumMath.EPSILON10); }); - it('produces correct value for flat array of positions', function() { - var positions = new ConstantProperty(Cartesian3.fromDegreesArray([-120.0, 40.0, - -119.0, 40.0, - -119.0, 41.0, - -120.0, 41.0])); - var property = new MinimumTerrainHeightProperty(positions); - - expect(property.getValue(time)).toEqualEpsilon(-382.8696126443784, CesiumMath.EPSILON10); - }); - it('equals works', function() { var positions = new ConstantProperty(); diff --git a/Specs/DataSources/RelativeToTerrainHeightPropertySpec.js b/Specs/DataSources/RelativeToTerrainHeightPropertySpec.js new file mode 100644 index 000000000000..db60a594dd0d --- /dev/null +++ b/Specs/DataSources/RelativeToTerrainHeightPropertySpec.js @@ -0,0 +1,166 @@ +defineSuite([ + 'DataSources/RelativeToTerrainHeightProperty', + 'Core/ApproximateTerrainHeights', + 'Core/Cartesian3', + 'Core/EllipsoidTerrainProvider', + 'Core/Event', + 'Core/ExtrapolationType', + 'Core/JulianDate', + 'Core/Math', + 'DataSources/CallbackProperty', + 'DataSources/ConstantProperty', + 'ThirdParty/when' +], function( + RelativeToTerrainHeightProperty, + ApproximateTerrainHeights, + Cartesian3, + EllipsoidTerrainProvider, + Event, + ExtrapolationType, + JulianDate, + CesiumMath, + CallbackProperty, + ConstantProperty, + when) { + 'use strict'; + + var time = JulianDate.now(); + var terrainProvider = new EllipsoidTerrainProvider(); + + it('can default construct', function() { + var property = new RelativeToTerrainHeightProperty(terrainProvider); + expect(property.isConstant).toBe(true); + expect(property.definitionChanged).toBeInstanceOf(Event); + expect(property.position).toBeUndefined(); + expect(property.heightRelativeToTerrain).toBeUndefined(); + expect(property.getValue(time)).toBe(0); + }); + + it('can construct with arguments', function() { + var position = new ConstantProperty(); + var heightRelativeToTerrain = new ConstantProperty(); + var property = new RelativeToTerrainHeightProperty(terrainProvider, position, heightRelativeToTerrain); + expect(property.isConstant).toBe(true); + expect(property.definitionChanged).toBeInstanceOf(Event); + expect(property.position).toBe(position); + expect(property.heightRelativeToTerrain).toBe(heightRelativeToTerrain); + }); + + it('raises definitionChanged event when position is set', function() { + var property = new RelativeToTerrainHeightProperty(terrainProvider); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + var position = new ConstantProperty(); + property.position = position; + expect(listener).toHaveBeenCalledWith(property, 'position', position, undefined); + }); + + it('raises definitionChanged event when heightRelativeToTerrain is set', function() { + var property = new RelativeToTerrainHeightProperty(terrainProvider); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + var heightRelativeToTerrain = new ConstantProperty(); + property.heightRelativeToTerrain = heightRelativeToTerrain; + expect(listener).toHaveBeenCalledWith(property, 'heightRelativeToTerrain', heightRelativeToTerrain, undefined); + }); + + it('subscribes and unsubscribes to position definitionChanged and propagates up', function() { + var position = new ConstantProperty(); + var property = new RelativeToTerrainHeightProperty(terrainProvider, position); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + //Position changing should raise out property change event + position.definitionChanged.raiseEvent(position); + expect(listener).toHaveBeenCalledWith(property, 'position', position, position); + + //Make sure it unsubscribes when value is changed + property.position = undefined; + + listener.calls.reset(); + position.definitionChanged.raiseEvent(position); + expect(listener.calls.count()).toBe(0); + }); + + it('subscribes and unsubscribes to heightRelativeToTerrain definitionChanged and propagates up', function() { + var heightRelativeToTerrain = new ConstantProperty(); + var property = new RelativeToTerrainHeightProperty(terrainProvider, undefined, heightRelativeToTerrain); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + //Position changing should raise out property change event + heightRelativeToTerrain.definitionChanged.raiseEvent(heightRelativeToTerrain); + expect(listener).toHaveBeenCalledWith(property, 'heightRelativeToTerrain', heightRelativeToTerrain, heightRelativeToTerrain); + + //Make sure it unsubscribes when value is changed + property.heightRelativeToTerrain = undefined; + + listener.calls.reset(); + heightRelativeToTerrain.definitionChanged.raiseEvent(heightRelativeToTerrain); + expect(listener.calls.count()).toBe(0); + }); + + it('does not raise definitionChanged event when position is set to the same instance', function() { + var position = new ConstantProperty(); + var heightRelativeToTerrain = new ConstantProperty(); + var property = new RelativeToTerrainHeightProperty(terrainProvider, position, heightRelativeToTerrain); + + var listener = jasmine.createSpy('listener'); + property.definitionChanged.addEventListener(listener); + + property.position = position; + property.heightRelativeToTerrain = heightRelativeToTerrain; + expect(listener.calls.count()).toBe(0); + }); + + it('produces correct value', function() { + var terrainHeight = 30.0; + spyOn(RelativeToTerrainHeightProperty, '_sampleTerrainMostDetailed').and.returnValue(when.resolve(terrainHeight)); + var position = new ConstantProperty(Cartesian3.fromDegrees(-120.0, 40.0)); + var heightRelativeToTerrain = new ConstantProperty(40.0); + var property = new RelativeToTerrainHeightProperty(terrainProvider, position, heightRelativeToTerrain); + + expect(property.getValue(time)).toEqual(70.0); + }); + + it('equals works', function() { + var position = new ConstantProperty(); + var heightRelativeToTerrain = new ConstantProperty(); + + var left = new RelativeToTerrainHeightProperty(terrainProvider); + var right = new RelativeToTerrainHeightProperty(terrainProvider); + + expect(left.equals(right)).toBe(true); + + left.position = position; + expect(left.equals(right)).toBe(false); + + right.position = position; + expect(left.equals(right)).toBe(true); + + left.heightRelativeToTerrain = heightRelativeToTerrain; + expect(left.equals(right)).toBe(false); + + right.heightRelativeToTerrain = heightRelativeToTerrain; + expect(left.equals(right)).toBe(false); + }); + + it('constructor throws without terrainProvider', function() { + expect(function() { + return new RelativeToTerrainHeightProperty(); + }).toThrowDeveloperError(); + }); + + it('getValue throws without time', function() { + var property = new RelativeToTerrainHeightProperty(terrainProvider); + expect(function() { + property.getValue(); + }).toThrowDeveloperError(); + }); +});