diff --git a/Apps/Sandcastle/gallery/Custom DataSource.html b/Apps/Sandcastle/gallery/Custom DataSource.html index 357c1856f104..af2defb2d817 100644 --- a/Apps/Sandcastle/gallery/Custom DataSource.html +++ b/Apps/Sandcastle/gallery/Custom DataSource.html @@ -156,14 +156,14 @@ set : function(value) { this._seriesToDisplay = value; - //Iterate over all polylines and set their show property + //Iterate over all entities and set their show property //to true only if they are part of the current series. var collection = this._entityCollection; var entities = collection.values; collection.suspendEvents(); for (var i = 0; i < entities.length; i++) { var entity = entities[i]; - entity.polyline.show.setValue(value === entity.seriesName); + entity.show = value === entity.seriesName; } collection.resumeEvents(); } @@ -287,7 +287,6 @@ //WebGL Globe only contains lines, so that's the only graphics we create. var polyline = new Cesium.PolylineGraphics(); - polyline.show = new Cesium.ConstantProperty(show); polyline.material = new Cesium.ColorMaterialProperty(color); polyline.width = new Cesium.ConstantProperty(2); polyline.followSurface = new Cesium.ConstantProperty(false); @@ -296,6 +295,7 @@ //The polyline instance itself needs to be on an entity. var entity = new Cesium.Entity({ id : seriesName + ' index ' + i.toString(), + show : show, polyline : polyline, seriesName : seriesName //Custom property to indicate series name }); diff --git a/Apps/Sandcastle/gallery/Show or Hide Entities.html b/Apps/Sandcastle/gallery/Show or Hide Entities.html new file mode 100644 index 000000000000..632b227a4e77 --- /dev/null +++ b/Apps/Sandcastle/gallery/Show or Hide Entities.html @@ -0,0 +1,95 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Show or Hide Entities.jpg b/Apps/Sandcastle/gallery/Show or Hide Entities.jpg new file mode 100644 index 000000000000..55907638f8a9 Binary files /dev/null and b/Apps/Sandcastle/gallery/Show or Hide Entities.jpg differ diff --git a/CHANGES.md b/CHANGES.md index ece02cdc48f1..b41af10c9af6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,7 +9,11 @@ Change Log * Removed `Camera.transform`, which was deprecated in Cesium 1.6. Use `Camera.lookAtTransform`. * Removed the `direction` and `up` options to `Camera.flyTo`, which were deprecated in Cesium 1.6. Use the `orientation` option. * Removed `Camera.flyToRectangle`, which was deprecated in Cesium 1.6. Use `Camera.flyTo`. +* Added `Entity.show` which is a boolean for easily hiding or showing an entity and its children. +* Added `Entity.isShowing` which is a read-only property that indicates if an entity is currently being drawn. +* Added support for the KML `visibility` element. * Added `PolylineArrowMaterialProperty` to allow entities materials to use polyline arrows. +* Improved `viewer.zoomTo` and `viewer.flyTo` so they are now "best effort" and work even if some entities being zoomed to are not currently in the scene. * Fixed `PointerEvent` detection so that it works with older implementations of the specification. This also fixes lack of mouse handling when detection failed, such as when using Cesium in the Windows `WebBrowser` control. * Fixed an issue with transparency. [#2572](https://github.com/AnalyticalGraphicsInc/cesium/issues/2572) * Fixed improper handling of null values when loading `GeoJSON` data. diff --git a/Source/Core/AssociativeArray.js b/Source/Core/AssociativeArray.js index 60bb46e2f971..006f801539b8 100644 --- a/Source/Core/AssociativeArray.js +++ b/Source/Core/AssociativeArray.js @@ -126,8 +126,11 @@ define([ * Clears the collection. */ AssociativeArray.prototype.removeAll = function() { - this._hash = {}; - this._array.length = 0; + var array = this._array; + if (array.length > 0) { + this._hash = {}; + array.length = 0; + } }; return AssociativeArray; diff --git a/Source/DataSources/BillboardVisualizer.js b/Source/DataSources/BillboardVisualizer.js index bfe6aba20056..50ec0c5a2548 100644 --- a/Source/DataSources/BillboardVisualizer.js +++ b/Source/DataSources/BillboardVisualizer.js @@ -105,7 +105,7 @@ define([ var billboardGraphics = entity._billboard; var textureValue; var billboard = item.billboard; - var show = entity.isAvailable(time) && Property.getValueOrDefault(billboardGraphics._show, time, true); + var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(billboardGraphics._show, time, true); if (show) { position = Property.getValueOrUndefined(entity._position, time, position); diff --git a/Source/DataSources/BoxGeometryUpdater.js b/Source/DataSources/BoxGeometryUpdater.js index 3df3b7bc9387..14f4628e5691 100644 --- a/Source/DataSources/BoxGeometryUpdater.js +++ b/Source/DataSources/BoxGeometryUpdater.js @@ -509,7 +509,7 @@ define([ var geometryUpdater = this._geometryUpdater; var entity = geometryUpdater._entity; var box = entity.box; - if (!entity.isAvailable(time) || !Property.getValueOrDefault(box.show, time, true)) { + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(box.show, time, true)) { return; } diff --git a/Source/DataSources/CorridorGeometryUpdater.js b/Source/DataSources/CorridorGeometryUpdater.js index fd69e0545208..43e08de143b8 100644 --- a/Source/DataSources/CorridorGeometryUpdater.js +++ b/Source/DataSources/CorridorGeometryUpdater.js @@ -529,7 +529,7 @@ define([ var geometryUpdater = this._geometryUpdater; var entity = geometryUpdater._entity; var corridor = entity.corridor; - if (!entity.isAvailable(time) || !Property.getValueOrDefault(corridor.show, time, true)) { + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(corridor.show, time, true)) { return; } diff --git a/Source/DataSources/CylinderGeometryUpdater.js b/Source/DataSources/CylinderGeometryUpdater.js index 924f2887e39c..ab66c94360e6 100644 --- a/Source/DataSources/CylinderGeometryUpdater.js +++ b/Source/DataSources/CylinderGeometryUpdater.js @@ -529,7 +529,7 @@ define([ var geometryUpdater = this._geometryUpdater; var entity = geometryUpdater._entity; var cylinder = entity.cylinder; - if (!entity.isAvailable(time) || !Property.getValueOrDefault(cylinder.show, time, true)) { + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(cylinder.show, time, true)) { return; } diff --git a/Source/DataSources/EllipseGeometryUpdater.js b/Source/DataSources/EllipseGeometryUpdater.js index 6ccbf208e6be..754a052ba329 100644 --- a/Source/DataSources/EllipseGeometryUpdater.js +++ b/Source/DataSources/EllipseGeometryUpdater.js @@ -541,7 +541,7 @@ define([ var geometryUpdater = this._geometryUpdater; var entity = geometryUpdater._entity; var ellipse = entity.ellipse; - if (!entity.isAvailable(time) || !Property.getValueOrDefault(ellipse.show, time, true)) { + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(ellipse.show, time, true)) { return; } diff --git a/Source/DataSources/EllipsoidGeometryUpdater.js b/Source/DataSources/EllipsoidGeometryUpdater.js index 858669ef58a9..fd030e46069c 100644 --- a/Source/DataSources/EllipsoidGeometryUpdater.js +++ b/Source/DataSources/EllipsoidGeometryUpdater.js @@ -535,7 +535,7 @@ define([ var entity = this._entity; var ellipsoid = entity.ellipsoid; - if (!entity.isAvailable(time) || !Property.getValueOrDefault(ellipsoid.show, time, true)) { + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(ellipsoid.show, time, true)) { if (defined(this._primitive)) { this._primitive.show = false; } diff --git a/Source/DataSources/Entity.js b/Source/DataSources/Entity.js index 269ee647870c..20b00c590ddd 100644 --- a/Source/DataSources/Entity.js +++ b/Source/DataSources/Entity.js @@ -92,6 +92,7 @@ define([ * @param {Object} [options] Object with the following properties: * @param {String} [options.id] A unique identifier for this object. If none is provided, a GUID is generated. * @param {String} [options.name] A human readable name to display to users. It does not have to be unique. + * @param {Boolean} [options.show] A boolean value indicating if the entity and its children are displayed. * @param {Property} [options.description] A string Property specifying an HTML description for this entity. * @param {PositionProperty} [options.position] A Property specifying the entity position. * @param {Property} [options.orientation] A Property specifying the entity orientation. @@ -132,8 +133,9 @@ define([ this._availability = undefined; this._id = id; this._definitionChanged = new Event(); - this._name = undefined; - this._parent = options.parent; + this._name = options.name; + this._show = defaultValue(options.show, true); + this._parent = undefined; this._propertyNames = ['billboard', 'box', 'corridor', 'cylinder', 'description', 'ellipse', // 'ellipsoid', 'label', 'model', 'orientation', 'path', 'point', 'polygon', // 'polyline', 'polylineVolume', 'position', 'rectangle', 'viewFrom', 'wall']; @@ -176,10 +178,27 @@ define([ this._viewFromSubscription = undefined; this._wall = undefined; this._wallSubscription = undefined; + this._children = []; - this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); + this.parent = options.parent; + this.merge(options); }; + function updateShow(entity, isShowing) { + var children = entity._children; + var length = children.length; + for (var i = 0; i < length; i++) { + var child = children[i]; + var childShow = child._show; + var oldValue = !isShowing && childShow; + var newValue = isShowing && childShow; + if (oldValue !== newValue) { + child._definitionChanged.raiseEvent(child, 'isShowing', newValue, oldValue); + } + } + entity._definitionChanged.raiseEvent(entity, 'isShowing', isShowing, !isShowing); + } + defineProperties(Entity.prototype, { /** * The availability, if any, associated with this object. @@ -219,17 +238,48 @@ define([ * @memberof Entity.prototype * @type {String} */ - name : { - configurable : false, + name : createRawPropertyDescriptor('name'), + /** + * Gets or sets whether this entity should be displayed. When set to true, + * the entity is only displayed if the parent entity's show property is also true. + * @memberof Entity.prototype + * @type {Boolean} + */ + show : { get : function() { - return this._name; + return this._show; }, set : function(value) { - var oldValue = this._name; - if (oldValue !== value) { - this._name = value; - this._definitionChanged.raiseEvent(this, 'name', value, oldValue); + //>>includeStart('debug', pragmas.debug); + if (!defined(value)) { + throw new DeveloperError('value is required.'); + } + //>>includeEnd('debug'); + + if (value === this._show) { + return; } + + var wasShowing = this.isShowing; + this._show = value; + var isShowing = this.isShowing; + + if (wasShowing !== isShowing) { + updateShow(this, isShowing); + } + + this._definitionChanged.raiseEvent(this, 'show', value, !value); + } + }, + /** + * Gets whether this entity is being displayed, taking into account + * the visibility of any ancestor entities. + * @memberof Entity.prototype + * @type {Boolean} + */ + isShowing : { + get : function() { + return this._show && (!defined(this._parent) || this._parent._show); } }, /** @@ -237,7 +287,35 @@ define([ * @memberof Entity.prototype * @type {Entity} */ - parent : createRawPropertyDescriptor('parent'), + parent : { + get : function() { + return this._parent; + }, + set : function(value) { + var oldValue = this._parent; + + if (oldValue === value) { + return; + } + + var wasShowing = this.isShowing; + if (defined(oldValue)) { + var index = oldValue._children.indexOf(this); + oldValue._children.splice(index, 1); + } + + this._parent = value; + value._children.push(this); + + var isShowing = this.isShowing; + + if (wasShowing !== isShowing) { + updateShow(this, isShowing); + } + + this._definitionChanged.raiseEvent(this, 'parent', value, oldValue); + } + }, /** * Gets the names of all properties registered on this instance. * @memberof Entity.prototype @@ -448,7 +526,8 @@ define([ } //>>includeEnd('debug'); - //Name and availability are not Property objects and are currently handled differently. + //Name, show, and availability are not Property objects and are currently handled differently. + //source.show is intentionally ignored because this.show always has a value. this.name = defaultValue(this.name, source.name); this.availability = defaultValue(source.availability, this.availability); diff --git a/Source/DataSources/KmlDataSource.js b/Source/DataSources/KmlDataSource.js index c2feb8089684..a4759abcc3fc 100644 --- a/Source/DataSources/KmlDataSource.js +++ b/Source/DataSources/KmlDataSource.js @@ -1299,8 +1299,8 @@ define([ } entity.availability = availability; - //var visibility = queryBooleanValue(featureNode, 'visibility', namespaces.kml); - //entity.uiShow = defaultValue(visibility, true); + var visibility = queryBooleanValue(featureNode, 'visibility', namespaces.kml); + entity.show = defaultValue(visibility, true); //var open = queryBooleanValue(featureNode, 'open', namespaces.kml); var authorNode = queryFirstNode(featureNode, 'author', namespaces.atom); diff --git a/Source/DataSources/LabelVisualizer.js b/Source/DataSources/LabelVisualizer.js index 1ea368210912..83b334942a7d 100644 --- a/Source/DataSources/LabelVisualizer.js +++ b/Source/DataSources/LabelVisualizer.js @@ -108,7 +108,7 @@ define([ var labelGraphics = entity._label; var text; var label = item.label; - var show = entity.isAvailable(time) && Property.getValueOrDefault(labelGraphics._show, time, true); + var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(labelGraphics._show, time, true); if (show) { position = Property.getValueOrUndefined(entity._position, time, position); diff --git a/Source/DataSources/ModelVisualizer.js b/Source/DataSources/ModelVisualizer.js index c2a6d92bab1e..cd40d0761c8e 100644 --- a/Source/DataSources/ModelVisualizer.js +++ b/Source/DataSources/ModelVisualizer.js @@ -84,7 +84,7 @@ define([ var uri; var modelData = modelHash[entity.id]; - var show = entity.isAvailable(time) && Property.getValueOrDefault(modelGraphics._show, time, true); + var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(modelGraphics._show, time, true); var modelMatrix; if (show) { diff --git a/Source/DataSources/PathVisualizer.js b/Source/DataSources/PathVisualizer.js index eefd2e564000..718c17610901 100644 --- a/Source/DataSources/PathVisualizer.js +++ b/Source/DataSources/PathVisualizer.js @@ -293,7 +293,7 @@ define([ var sampleStop; var showProperty = pathGraphics._show; var polyline = item.polyline; - var show = !defined(showProperty) || showProperty.getValue(time); + var show = entity.isShowing && (!defined(showProperty) || showProperty.getValue(time)); //While we want to show the path, there may not actually be anything to show //depending on lead/trail settings. Compute the interval of the path to diff --git a/Source/DataSources/PointVisualizer.js b/Source/DataSources/PointVisualizer.js index 919edaf69232..ff86978db82a 100644 --- a/Source/DataSources/PointVisualizer.js +++ b/Source/DataSources/PointVisualizer.js @@ -91,7 +91,7 @@ define([ var entity = item.entity; var pointGraphics = entity._point; var billboard = item.billboard; - var show = entity.isAvailable(time) && Property.getValueOrDefault(pointGraphics._show, time, true); + var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(pointGraphics._show, time, true); if (show) { position = Property.getValueOrUndefined(entity._position, time, position); show = defined(position); diff --git a/Source/DataSources/PolygonGeometryUpdater.js b/Source/DataSources/PolygonGeometryUpdater.js index ea0739b3e025..f9ab20c0e8b4 100644 --- a/Source/DataSources/PolygonGeometryUpdater.js +++ b/Source/DataSources/PolygonGeometryUpdater.js @@ -542,7 +542,7 @@ define([ var geometryUpdater = this._geometryUpdater; var entity = geometryUpdater._entity; var polygon = entity.polygon; - if (!entity.isAvailable(time) || !Property.getValueOrDefault(polygon.show, time, true)) { + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polygon.show, time, true)) { return; } diff --git a/Source/DataSources/PolylineGeometryUpdater.js b/Source/DataSources/PolylineGeometryUpdater.js index 54ea6adffaa0..3793cbdffb16 100644 --- a/Source/DataSources/PolylineGeometryUpdater.js +++ b/Source/DataSources/PolylineGeometryUpdater.js @@ -460,7 +460,7 @@ define([ var polyline = entity.polyline; var line = this._line; - if (!entity.isAvailable(time) || !Property.getValueOrDefault(polyline._show, time, true)) { + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polyline._show, time, true)) { line.show = false; return; } diff --git a/Source/DataSources/PolylineVolumeGeometryUpdater.js b/Source/DataSources/PolylineVolumeGeometryUpdater.js index 48d5d8df37b2..03b28c993446 100644 --- a/Source/DataSources/PolylineVolumeGeometryUpdater.js +++ b/Source/DataSources/PolylineVolumeGeometryUpdater.js @@ -516,7 +516,7 @@ define([ var geometryUpdater = this._geometryUpdater; var entity = geometryUpdater._entity; var polylineVolume = entity.polylineVolume; - if (!entity.isAvailable(time) || !Property.getValueOrDefault(polylineVolume.show, time, true)) { + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polylineVolume.show, time, true)) { return; } diff --git a/Source/DataSources/RectangleGeometryUpdater.js b/Source/DataSources/RectangleGeometryUpdater.js index 2781f1c4fd9a..2e608b033427 100644 --- a/Source/DataSources/RectangleGeometryUpdater.js +++ b/Source/DataSources/RectangleGeometryUpdater.js @@ -537,7 +537,7 @@ define([ var geometryUpdater = this._geometryUpdater; var entity = geometryUpdater._entity; var rectangle = entity.rectangle; - if (!entity.isAvailable(time) || !Property.getValueOrDefault(rectangle.show, time, true)) { + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(rectangle.show, time, true)) { return; } diff --git a/Source/DataSources/StaticGeometryColorBatch.js b/Source/DataSources/StaticGeometryColorBatch.js index ee34a12c8e89..900fdc81d448 100644 --- a/Source/DataSources/StaticGeometryColorBatch.js +++ b/Source/DataSources/StaticGeometryColorBatch.js @@ -31,6 +31,8 @@ define([ this.updaters = new AssociativeArray(); this.updatersWithAttributes = new AssociativeArray(); this.attributes = new AssociativeArray(); + this.subscriptions = new AssociativeArray(); + this.showsUpdated = new AssociativeArray(); this.itemsToRemove = []; }; @@ -41,14 +43,27 @@ define([ this.updaters.set(id, updater); if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant) { this.updatersWithAttributes.set(id, updater); + } else { + var that = this; + this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) { + if (propertyName === 'isShowing') { + that.showsUpdated.set(entity.id, updater); + } + })); } }; Batch.prototype.remove = function(updater) { var id = updater.entity.id; this.createPrimitive = this.geometry.remove(id) || this.createPrimitive; - this.updaters.remove(id); - this.updatersWithAttributes.remove(id); + if (this.updaters.remove(id)) { + this.updatersWithAttributes.remove(id); + var unsubscribe = this.subscriptions.get(id); + if (defined(unsubscribe)) { + unsubscribe(); + this.subscriptions.remove(id); + } + } }; Batch.prototype.update = function(time) { @@ -110,13 +125,15 @@ define([ } if (!updater.hasConstantFill) { - var show = updater.isFilled(time); + var show = updater.entity.isShowing && updater.isFilled(time); var currentShow = attributes.show[0] === 1; if (show !== currentShow) { attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); } } } + + this.updateShows(primitive); } else if (defined(primitive) && !primitive.ready) { isUpdated = false; } @@ -124,6 +141,28 @@ define([ return isUpdated; }; + Batch.prototype.updateShows = function(primitive) { + var showsUpdated = this.showsUpdated.values; + var length = showsUpdated.length; + for (var i = 0; i < length; i++) { + var updater = showsUpdated[i]; + var instance = this.geometry.get(updater.entity.id); + + var attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); + } + + var show = updater.entity.isShowing; + var currentShow = attributes.show[0] === 1; + if (show !== currentShow) { + attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); + } + } + this.showsUpdated.removeAll(); + }; + Batch.prototype.contains = function(entity) { return this.updaters.contains(entity.id); }; diff --git a/Source/DataSources/StaticGeometryPerMaterialBatch.js b/Source/DataSources/StaticGeometryPerMaterialBatch.js index 9bbac9f6c6d5..2f13bb54c524 100644 --- a/Source/DataSources/StaticGeometryPerMaterialBatch.js +++ b/Source/DataSources/StaticGeometryPerMaterialBatch.js @@ -30,6 +30,8 @@ define([ this.attributes = new AssociativeArray(); this.invalidated = false; this.removeMaterialSubscription = materialProperty.definitionChanged.addEventListener(Batch.prototype.onMaterialChanged, this); + this.subscriptions = new AssociativeArray(); + this.showsUpdated = new AssociativeArray(); }; Batch.prototype.onMaterialChanged = function() { @@ -54,16 +56,32 @@ define([ this.geometry.set(id, updater.createFillGeometryInstance(time)); if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant) { this.updatersWithAttributes.set(id, updater); + } else { + var that = this; + this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) { + if (propertyName === 'isShowing') { + that.showsUpdated.set(entity.id, updater); + } + })); } this.createPrimitive = true; }; Batch.prototype.remove = function(updater) { var id = updater.entity.id; - this.createPrimitive = this.updaters.remove(id); - this.geometry.remove(id); - this.updatersWithAttributes.remove(id); - return this.createPrimitive; + var createPrimitive = this.updaters.remove(id); + + if (createPrimitive) { + this.geometry.remove(id); + this.updatersWithAttributes.remove(id); + var unsubscribe = this.subscriptions.get(id); + if (defined(unsubscribe)) { + unsubscribe(); + this.subscriptions.remove(id); + } + } + this.createPrimitive = createPrimitive; + return createPrimitive; }; Batch.prototype.update = function(time) { @@ -109,7 +127,8 @@ define([ var length = updatersWithAttributes.length; for (var i = 0; i < length; i++) { var updater = updatersWithAttributes[i]; - var instance = this.geometry.get(updater.entity.id); + var entity = updater.entity; + var instance = this.geometry.get(entity.id); var attributes = this.attributes.get(instance.id.id); if (!defined(attributes)) { @@ -118,19 +137,44 @@ define([ } if (!updater.hasConstantFill) { - var show = updater.isFilled(time); + var show = entity.isShowing && updater.isFilled(time); var currentShow = attributes.show[0] === 1; if (show !== currentShow) { attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); } } } + + this.updateShows(primitive); } else if (defined(primitive) && !primitive.ready) { isUpdated = false; } return isUpdated; }; + Batch.prototype.updateShows = function(primitive) { + var showsUpdated = this.showsUpdated.values; + var length = showsUpdated.length; + for (var i = 0; i < length; i++) { + var updater = showsUpdated[i]; + var entity = updater.entity; + var instance = this.geometry.get(entity.id); + + var attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); + } + + var show = entity.isShowing; + var currentShow = attributes.show[0] === 1; + if (show !== currentShow) { + attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); + } + } + this.showsUpdated.removeAll(); + }; + Batch.prototype.contains = function(entity) { return this.updaters.contains(entity.id); }; diff --git a/Source/DataSources/StaticOutlineGeometryBatch.js b/Source/DataSources/StaticOutlineGeometryBatch.js index 13c6baa6abd2..6b1f0715d419 100644 --- a/Source/DataSources/StaticOutlineGeometryBatch.js +++ b/Source/DataSources/StaticOutlineGeometryBatch.js @@ -31,6 +31,8 @@ define([ this.attributes = new AssociativeArray(); this.itemsToRemove = []; this.width = width; + this.subscriptions = new AssociativeArray(); + this.showsUpdated = new AssociativeArray(); }; Batch.prototype.add = function(updater, instance) { @@ -40,14 +42,27 @@ define([ this.updaters.set(id, updater); if (!updater.hasConstantOutline || !updater.outlineColorProperty.isConstant) { this.updatersWithAttributes.set(id, updater); + } else { + var that = this; + this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) { + if (propertyName === 'isShowing') { + that.showsUpdated.set(entity.id, updater); + } + })); } }; Batch.prototype.remove = function(updater) { var id = updater.entity.id; this.createPrimitive = this.geometry.remove(id) || this.createPrimitive; - this.updaters.remove(id); - this.updatersWithAttributes.remove(id); + if (this.updaters.remove(id)) { + this.updatersWithAttributes.remove(id); + var unsubscribe = this.subscriptions.get(id); + if (defined(unsubscribe)) { + unsubscribe(); + this.subscriptions.remove(id); + } + } }; var colorScratch = new Color(); @@ -115,13 +130,15 @@ define([ } if (!updater.hasConstantOutline) { - var show = updater.isOutlineVisible(time); + var show = updater.entity.isShowing && updater.isOutlineVisible(time); var currentShow = attributes.show[0] === 1; if (show !== currentShow) { attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); } } } + + this.updateShows(primitive); } else if (defined(primitive) && !primitive.ready) { isUpdated = false; } @@ -130,6 +147,28 @@ define([ return isUpdated; }; + Batch.prototype.updateShows = function(primitive) { + var showsUpdated = this.showsUpdated.values; + var length = showsUpdated.length; + for (var i = 0; i < length; i++) { + var updater = showsUpdated[i]; + var instance = this.geometry.get(updater.entity.id); + + var attributes = this.attributes.get(instance.id.id); + if (!defined(attributes)) { + attributes = primitive.getGeometryInstanceAttributes(instance.id); + this.attributes.set(instance.id.id, attributes); + } + + var show = updater.entity.isShowing; + var currentShow = attributes.show[0] === 1; + if (show !== currentShow) { + attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show); + } + } + this.showsUpdated.removeAll(); + }; + Batch.prototype.contains = function(entity) { return this.updaters.contains(entity.id); }; diff --git a/Source/DataSources/WallGeometryUpdater.js b/Source/DataSources/WallGeometryUpdater.js index 3dafe16ab952..68997bc9f415 100644 --- a/Source/DataSources/WallGeometryUpdater.js +++ b/Source/DataSources/WallGeometryUpdater.js @@ -519,7 +519,7 @@ define([ var geometryUpdater = this._geometryUpdater; var entity = geometryUpdater._entity; var wall = entity.wall; - if (!entity.isAvailable(time) || !Property.getValueOrDefault(wall.show, time, true)) { + if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(wall.show, time, true)) { return; } diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js index aeed8016fe45..f189352be69f 100644 --- a/Source/Widgets/Viewer/Viewer.js +++ b/Source/Widgets/Viewer/Viewer.js @@ -1298,7 +1298,7 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to var selectedEntity = this.selectedEntity; var showSelection = defined(selectedEntity) && this._enableInfoOrSelection; - if (showSelection && selectedEntity.isAvailable(time)) { + if (showSelection && selectedEntity.isShowing && selectedEntity.isAvailable(time)) { var state = this._dataSourceDisplay.getBoundingSphere(selectedEntity, true, boundingSphereScratch); if (state !== BoundingSphereState.FAILED) { position = boundingSphereScratch.center; @@ -1561,12 +1561,9 @@ Either specify options.terrainProvider instead or set options.baseLayerPicker to if (state === BoundingSphereState.PENDING) { return; - } else if (state === BoundingSphereState.FAILED) { - cancelZoom(viewer); - return; + } else if (state !== BoundingSphereState.FAILED) { + boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch)); } - - boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch)); } if (boundingSpheres.length === 0) { diff --git a/Specs/DataSources/BoxGeometryUpdaterSpec.js b/Specs/DataSources/BoxGeometryUpdaterSpec.js index 7e87e2c16935..a8115a85217d 100644 --- a/Specs/DataSources/BoxGeometryUpdaterSpec.js +++ b/Specs/DataSources/BoxGeometryUpdaterSpec.js @@ -298,6 +298,11 @@ defineSuite([ expect(dynamicUpdater._options.id).toBe(entity); expect(dynamicUpdater._options.dimensions).toEqual(box.dimensions.getValue()); + entity.show = false; + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(0); + entity.show = true; + box.show.setValue(false); dynamicUpdater.update(JulianDate.now()); expect(primitives.length).toBe(0); diff --git a/Specs/DataSources/CorridorGeometryUpdaterSpec.js b/Specs/DataSources/CorridorGeometryUpdaterSpec.js index bb04911a2525..a0441bfb3a9e 100644 --- a/Specs/DataSources/CorridorGeometryUpdaterSpec.js +++ b/Specs/DataSources/CorridorGeometryUpdaterSpec.js @@ -400,6 +400,11 @@ defineSuite([ expect(options.granularity).toEqual(corridor.granularity.getValue()); expect(options.cornerType).toEqual(corridor.cornerType.getValue()); + entity.show = false; + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(0); + entity.show = true; + //If a dynamic show returns false, the primitive should go away. corridor.show.setValue(false); dynamicUpdater.update(time); diff --git a/Specs/DataSources/CylinderGeometryUpdaterSpec.js b/Specs/DataSources/CylinderGeometryUpdaterSpec.js index 5bfbd3e7a47e..52a5825b72b4 100644 --- a/Specs/DataSources/CylinderGeometryUpdaterSpec.js +++ b/Specs/DataSources/CylinderGeometryUpdaterSpec.js @@ -384,6 +384,11 @@ defineSuite([ expect(dynamicUpdater._options.topRadius).toEqual(cylinder.topRadius.getValue()); expect(dynamicUpdater._options.bottomRadius).toEqual(cylinder.bottomRadius.getValue()); + entity.show = false; + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(0); + entity.show = true; + cylinder.show.setValue(false); dynamicUpdater.update(JulianDate.now()); expect(primitives.length).toBe(0); diff --git a/Specs/DataSources/EllipseGeometryUpdaterSpec.js b/Specs/DataSources/EllipseGeometryUpdaterSpec.js index 72a5e0689b5a..5f5fa040df80 100644 --- a/Specs/DataSources/EllipseGeometryUpdaterSpec.js +++ b/Specs/DataSources/EllipseGeometryUpdaterSpec.js @@ -436,6 +436,11 @@ defineSuite([ expect(dynamicUpdater._options.semiMajorAxis).toEqual(ellipse.semiMajorAxis.getValue()); expect(dynamicUpdater._options.semiMinorAxis).toEqual(ellipse.semiMinorAxis.getValue()); + entity.show = false; + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(0); + entity.show = true; + ellipse.show.setValue(false); dynamicUpdater.update(JulianDate.now()); expect(primitives.length).toBe(0); diff --git a/Specs/DataSources/EntitySpec.js b/Specs/DataSources/EntitySpec.js index c606b856c5f3..57e5a5e5facb 100644 --- a/Specs/DataSources/EntitySpec.js +++ b/Specs/DataSources/EntitySpec.js @@ -83,6 +83,7 @@ defineSuite([ var options = { id : 'someId', name : 'bob', + show : false, availability : new TimeIntervalCollection(), parent : new Entity(), customProperty : {}, @@ -110,6 +111,7 @@ defineSuite([ entity = new Entity(options); expect(entity.id).toEqual(options.id); expect(entity.name).toEqual(options.name); + expect(entity.show).toBe(options.show); expect(entity.availability).toBe(options.availability); expect(entity.parent).toBe(options.parent); expect(entity.customProperty).toBe(options.customProperty); @@ -357,4 +359,91 @@ defineSuite([ entity.removeProperty('name'); }).toThrowDeveloperError(); }); + + it('isShowing works without parent.', function() { + var entity = new Entity({ + show : false + }); + expect(entity.isShowing).toBe(false); + + var listener = jasmine.createSpy('listener'); + entity.definitionChanged.addEventListener(listener); + + entity.show = true; + expect(listener.calls.count()).toBe(2); + expect(listener.calls.argsFor(0)).toEqual([entity, 'isShowing', true, false]); + expect(listener.calls.argsFor(1)).toEqual([entity, 'show', true, false]); + expect(entity.isShowing).toBe(true); + + listener.calls.reset(); + + entity.show = false; + expect(listener.calls.count()).toBe(2); + expect(listener.calls.argsFor(0)).toEqual([entity, 'isShowing', false, true]); + expect(listener.calls.argsFor(1)).toEqual([entity, 'show', false, true]); + expect(entity.isShowing).toBe(false); + }); + + it('isShowing works with parent.', function() { + var entity = new Entity(); + entity.parent = new Entity(); + + var listener = jasmine.createSpy('listener'); + entity.definitionChanged.addEventListener(listener); + + entity.parent.show = false; + + //Setting entity.parent show to false causes entity to raise + //its own isShowing event, but not the show event. + expect(listener.calls.count()).toBe(1); + expect(listener.calls.argsFor(0)).toEqual([entity, 'isShowing', false, true]); + expect(entity.show).toBe(true); + expect(entity.isShowing).toBe(false); + + listener.calls.reset(); + + //Since isShowing is already false, setting show to false causes the show event + //but not the isShowing event to be raised + entity.show = false; + expect(entity.show).toBe(false); + expect(listener.calls.count()).toBe(1); + expect(listener.calls.argsFor(0)).toEqual([entity, 'show', false, true]); + + listener.calls.reset(); + + //Setting parent show to true does not trigger the entity.isShowing event + //because entity.show is false; + entity.parent.show = true; + expect(entity.show).toBe(false); + expect(entity.isShowing).toBe(false); + expect(listener.calls.count()).toBe(0); + + listener.calls.reset(); + + //Setting entity.show to try now causes both events to be raised + //because the parent is also showing. + entity.show = true; + expect(listener.calls.count()).toBe(2); + expect(listener.calls.argsFor(0)).toEqual([entity, 'isShowing', true, false]); + expect(listener.calls.argsFor(1)).toEqual([entity, 'show', true, false]); + expect(entity.show).toBe(true); + expect(entity.isShowing).toBe(true); + }); + + it('isShowing works when replacing parent.', function() { + var entity = new Entity(); + entity.parent = new Entity(); + + var listener = jasmine.createSpy('listener'); + entity.definitionChanged.addEventListener(listener); + + entity.parent = new Entity({ + show : false + }); + + expect(listener.calls.count()).toBe(2); + expect(listener.calls.argsFor(0)).toEqual([entity, 'isShowing', false, true]); + expect(entity.show).toBe(true); + expect(entity.isShowing).toBe(false); + }); }); \ No newline at end of file diff --git a/Specs/DataSources/KmlDataSourceSpec.js b/Specs/DataSources/KmlDataSourceSpec.js index e0334d374827..7d16b0f754a7 100644 --- a/Specs/DataSources/KmlDataSourceSpec.js +++ b/Specs/DataSources/KmlDataSourceSpec.js @@ -527,6 +527,18 @@ defineSuite([ }); }); + it('Feature: visibility works', function() { + var kml = '\ + \ + 0\ + '; + + return KmlDataSource.load(parser.parseFromString(kml, "text/xml")).then(function(dataSource) { + var entity = dataSource.entities.values[0]; + expect(entity.show).toBe(false); + }); + }); + it('Feature: TimeStamp gracefully handles empty fields', function() { var kml = '\ \ diff --git a/Specs/DataSources/PolygonGeometryUpdaterSpec.js b/Specs/DataSources/PolygonGeometryUpdaterSpec.js index 5832d353fd23..6e57d843ff59 100644 --- a/Specs/DataSources/PolygonGeometryUpdaterSpec.js +++ b/Specs/DataSources/PolygonGeometryUpdaterSpec.js @@ -395,6 +395,11 @@ defineSuite([ expect(options.granularity).toEqual(polygon.granularity.getValue()); expect(options.stRotation).toEqual(polygon.stRotation.getValue()); + entity.show = false; + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(0); + entity.show = true; + //If a dynamic show returns false, the primitive should go away. polygon.show.setValue(false); dynamicUpdater.update(time); diff --git a/Specs/DataSources/PolylineVolumeGeometryUpdaterSpec.js b/Specs/DataSources/PolylineVolumeGeometryUpdaterSpec.js index 9d146a243b08..a66bacd65dfe 100644 --- a/Specs/DataSources/PolylineVolumeGeometryUpdaterSpec.js +++ b/Specs/DataSources/PolylineVolumeGeometryUpdaterSpec.js @@ -354,6 +354,11 @@ defineSuite([ expect(options.granularity).toEqual(polylineVolume.granularity.getValue()); expect(options.cornerType).toEqual(polylineVolume.cornerType.getValue()); + entity.show = false; + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(0); + entity.show = true; + //If a dynamic show returns false, the primitive should go away. polylineVolume.show.setValue(false); dynamicUpdater.update(time); diff --git a/Specs/DataSources/RectangleGeometryUpdaterSpec.js b/Specs/DataSources/RectangleGeometryUpdaterSpec.js index 4091609e1193..150c3e62a511 100644 --- a/Specs/DataSources/RectangleGeometryUpdaterSpec.js +++ b/Specs/DataSources/RectangleGeometryUpdaterSpec.js @@ -399,6 +399,11 @@ defineSuite([ expect(options.granularity).toEqual(rectangle.granularity.getValue()); expect(options.stRotation).toEqual(rectangle.stRotation.getValue()); + entity.show = false; + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(0); + entity.show = true; + //If a dynamic show returns false, the primitive should go away. rectangle.show.setValue(false); dynamicUpdater.update(time); diff --git a/Specs/DataSources/WallGeometryUpdaterSpec.js b/Specs/DataSources/WallGeometryUpdaterSpec.js index 981026b529ea..7f4201ede4d2 100644 --- a/Specs/DataSources/WallGeometryUpdaterSpec.js +++ b/Specs/DataSources/WallGeometryUpdaterSpec.js @@ -361,6 +361,11 @@ defineSuite([ expect(options.maximumHeights).toEqual(wall.maximumHeights.getValue()); expect(options.granularity).toEqual(wall.granularity.getValue()); + entity.show = false; + dynamicUpdater.update(JulianDate.now()); + expect(primitives.length).toBe(0); + entity.show = true; + //If a dynamic show returns false, the primitive should go away. wall.show.setValue(false); dynamicUpdater.update(time);