defineSuite([ 'DataSources/PathVisualizer', 'Core/Cartesian3', 'Core/Color', 'Core/DistanceDisplayCondition', 'Core/JulianDate', 'Core/Matrix4', 'Core/ReferenceFrame', 'Core/TimeInterval', 'DataSources/CompositePositionProperty', 'DataSources/ConstantPositionProperty', 'DataSources/ConstantProperty', 'DataSources/EntityCollection', 'DataSources/PathGraphics', 'DataSources/PolylineGlowMaterialProperty', 'DataSources/PolylineOutlineMaterialProperty', 'DataSources/ReferenceProperty', 'DataSources/SampledPositionProperty', 'DataSources/ScaledPositionProperty', 'DataSources/TimeIntervalCollectionPositionProperty', 'Scene/SceneMode', 'Specs/createScene' ], function( PathVisualizer, Cartesian3, Color, DistanceDisplayCondition, JulianDate, Matrix4, ReferenceFrame, TimeInterval, CompositePositionProperty, ConstantPositionProperty, ConstantProperty, EntityCollection, PathGraphics, PolylineGlowMaterialProperty, PolylineOutlineMaterialProperty, ReferenceProperty, SampledPositionProperty, ScaledPositionProperty, TimeIntervalCollectionPositionProperty, SceneMode, createScene) { 'use strict'; var scene; var visualizer; beforeAll(function() { scene = createScene(); }); afterAll(function() { scene.destroyForSpecs(); }); afterEach(function() { visualizer = visualizer && visualizer.destroy(); }); it('constructor throws if no scene is passed.', function() { expect(function() { return new PathVisualizer(); }).toThrowDeveloperError(); }); it('update throws if no time specified.', function() { var entityCollection = new EntityCollection(); visualizer = new PathVisualizer(scene, entityCollection); expect(function() { visualizer.update(); }).toThrowDeveloperError(); }); it('isDestroy returns false until destroyed.', function() { var entityCollection = new EntityCollection(); visualizer = new PathVisualizer(scene, entityCollection); expect(visualizer.isDestroyed()).toEqual(false); visualizer.destroy(); expect(visualizer.isDestroyed()).toEqual(true); visualizer = undefined; }); it('removes the listener from the entity collection when destroyed', function() { var entityCollection = new EntityCollection(); var visualizer = new PathVisualizer(scene, entityCollection); expect(entityCollection.collectionChanged.numberOfListeners).toEqual(1); visualizer.destroy(); expect(entityCollection.collectionChanged.numberOfListeners).toEqual(0); }); it('object with no path does not create one.', function() { var entityCollection = new EntityCollection(); visualizer = new PathVisualizer(scene, entityCollection); var testObject = entityCollection.getOrCreateEntity('test'); testObject.position = new ConstantProperty([new Cartesian3(1234, 5678, 9101112), new Cartesian3(5678, 1234, 1101112)]); visualizer.update(JulianDate.now()); expect(scene.primitives.length).toEqual(0); }); it('object with no position does not create a polyline.', function() { var entityCollection = new EntityCollection(); visualizer = new PathVisualizer(scene, entityCollection); var testObject = entityCollection.getOrCreateEntity('test'); var path = testObject.path = new PathGraphics(); path.show = new ConstantProperty(true); visualizer.update(JulianDate.now()); expect(scene.primitives.length).toEqual(0); }); it('adding and removing an entity path without rendering does not crash.', function() { var times = [new JulianDate(0, 0), new JulianDate(1, 0)]; var positions = [new Cartesian3(1234, 5678, 9101112), new Cartesian3(5678, 1234, 1101112)]; var entityCollection = new EntityCollection(); visualizer = new PathVisualizer(scene, entityCollection); var position = new SampledPositionProperty(); position.addSamples(times, positions); var testObject = entityCollection.getOrCreateEntity('test'); testObject.position = position; testObject.path = new PathGraphics(); //Before we fixed the issue, the below remove call would cause a crash //when visualizer.update was not called at least once after the entity was added. entityCollection.remove(testObject); }); it('A PathGraphics causes a primitive to be created and updated.', function() { var times = [new JulianDate(0, 0), new JulianDate(1, 0)]; var updateTime = new JulianDate(0.5, 0); var positions = [new Cartesian3(1234, 5678, 9101112), new Cartesian3(5678, 1234, 1101112)]; var entityCollection = new EntityCollection(); visualizer = new PathVisualizer(scene, entityCollection); expect(scene.primitives.length).toEqual(0); var testObject = entityCollection.getOrCreateEntity('test'); var position = new SampledPositionProperty(); testObject.position = position; position.addSamples(times, positions); var path = testObject.path = new PathGraphics(); path.show = new ConstantProperty(true); path.material = new PolylineOutlineMaterialProperty(); path.material.color = new ConstantProperty(new Color(0.8, 0.7, 0.6, 0.5)); path.material.outlineColor = new ConstantProperty(new Color(0.1, 0.2, 0.3, 0.4)); path.material.outlineWidth = new ConstantProperty(2.5); path.width = new ConstantProperty(12.5); path.distanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition(10.0, 20.0)); path.leadTime = new ConstantProperty(25); path.trailTime = new ConstantProperty(10); visualizer.update(updateTime); expect(scene.primitives.length).toEqual(1); var polylineCollection = scene.primitives.get(0); var primitive = polylineCollection.get(0); expect(primitive.positions[0]).toEqual(testObject.position.getValue(JulianDate.addSeconds(updateTime, -path.trailTime.getValue(), new JulianDate()))); expect(primitive.positions[1]).toEqual(testObject.position.getValue(updateTime)); expect(primitive.positions[2]).toEqual(testObject.position.getValue(JulianDate.addSeconds(updateTime, path.leadTime.getValue(), new JulianDate()))); expect(primitive.show).toEqual(testObject.path.show.getValue(updateTime)); expect(primitive.width).toEqual(testObject.path.width.getValue(updateTime)); expect(primitive.distanceDisplayCondition).toEqual(testObject.path.distanceDisplayCondition.getValue(updateTime)); var material = primitive.material; expect(material.uniforms.color).toEqual(testObject.path.material.color.getValue(updateTime)); expect(material.uniforms.outlineColor).toEqual(testObject.path.material.outlineColor.getValue(updateTime)); expect(material.uniforms.outlineWidth).toEqual(testObject.path.material.outlineWidth.getValue(updateTime)); path.show = new ConstantProperty(false); visualizer.update(updateTime); expect(primitive.show).toEqual(testObject.path.show.getValue(updateTime)); }); it('creates primitives when an entity is already in the collection.', function() { var times = [new JulianDate(0, 0), new JulianDate(1, 0)]; var updateTime = new JulianDate(0.5, 0); var positions = [new Cartesian3(1234, 5678, 9101112), new Cartesian3(5678, 1234, 1101112)]; var entityCollection = new EntityCollection(); var testObject = entityCollection.getOrCreateEntity('test'); var position = new SampledPositionProperty(); testObject.position = position; position.addSamples(times, positions); var path = testObject.path = new PathGraphics(); path.show = new ConstantProperty(true); path.material = new PolylineOutlineMaterialProperty(); path.material.color = new ConstantProperty(new Color(0.8, 0.7, 0.6, 0.5)); path.material.outlineColor = new ConstantProperty(new Color(0.1, 0.2, 0.3, 0.4)); path.material.outlineWidth = new ConstantProperty(2.5); path.width = new ConstantProperty(12.5); path.leadTime = new ConstantProperty(25); path.trailTime = new ConstantProperty(10); visualizer = new PathVisualizer(scene, entityCollection); expect(scene.primitives.length).toEqual(0); visualizer.update(updateTime); expect(scene.primitives.length).toEqual(1); }); it('A custom material can be used.', function() { var times = [new JulianDate(0, 0), new JulianDate(1, 0)]; var updateTime = new JulianDate(0.5, 0); var positions = [new Cartesian3(1234, 5678, 9101112), new Cartesian3(5678, 1234, 1101112)]; var entityCollection = new EntityCollection(); visualizer = new PathVisualizer(scene, entityCollection); expect(scene.primitives.length).toEqual(0); var testObject = entityCollection.getOrCreateEntity('test'); var position = new SampledPositionProperty(); testObject.position = position; position.addSamples(times, positions); var path = testObject.path = new PathGraphics(); path.show = new ConstantProperty(true); path.material = new PolylineGlowMaterialProperty(); path.material.color = new ConstantProperty(new Color(0.8, 0.7, 0.6, 0.5)); path.material.glowPower = new ConstantProperty(0.2); path.width = new ConstantProperty(12.5); path.leadTime = new ConstantProperty(25); path.trailTime = new ConstantProperty(10); visualizer.update(updateTime); expect(scene.primitives.length).toEqual(1); var polylineCollection = scene.primitives.get(0); var primitive = polylineCollection.get(0); var material = primitive.material; expect(material.uniforms.color).toEqual(testObject.path.material.color.getValue(updateTime)); expect(material.uniforms.glowPower).toEqual(testObject.path.material.glowPower.getValue(updateTime)); }); it('Reuses primitives when hiding one and showing another', function() { var times = [new JulianDate(0, 0), new JulianDate(1, 0)]; var time = new JulianDate(0.5, 0); var positions = [new Cartesian3(1234, 5678, 9101112), new Cartesian3(5678, 1234, 1101112)]; var entityCollection = new EntityCollection(); visualizer = new PathVisualizer(scene, entityCollection); var position = new SampledPositionProperty(); position.addSamples(times, positions); var testObject = entityCollection.getOrCreateEntity('test'); testObject.position = position; testObject.path = new PathGraphics(); testObject.path.show = new ConstantProperty(true); testObject.path.leadTime = new ConstantProperty(25); testObject.path.trailTime = new ConstantProperty(10); visualizer.update(time); var polylineCollection = scene.primitives.get(0); expect(polylineCollection.length).toEqual(1); testObject.path.show = new ConstantProperty(false); visualizer.update(time); expect(polylineCollection.length).toEqual(1); position = new SampledPositionProperty(); position.addSamples(times, positions); var testObject2 = entityCollection.getOrCreateEntity('test2'); testObject2.position = position; testObject2.path = new PathGraphics(); testObject2.path.show = new ConstantProperty(true); testObject2.path.leadTime = new ConstantProperty(25); testObject2.path.trailTime = new ConstantProperty(10); visualizer.update(time); expect(polylineCollection.length).toEqual(1); }); it('Switches from inertial to fixed paths in 2D', function() { var times = [new JulianDate(0, 0), new JulianDate(1, 0)]; var time = new JulianDate(0.5, 0); var positions = [new Cartesian3(1234, 5678, 9101112), new Cartesian3(5678, 1234, 1101112)]; var position = new SampledPositionProperty(ReferenceFrame.INERTIAL); position.addSamples(times, positions); var entityCollection = new EntityCollection(); visualizer = new PathVisualizer(scene, entityCollection); var testObject = entityCollection.getOrCreateEntity('test'); testObject.position = position; testObject.path = new PathGraphics(); testObject.path.leadTime = new ConstantProperty(25); testObject.path.trailTime = new ConstantProperty(10); visualizer.update(time); expect(scene.primitives.length).toEqual(1); //They'll be one inertial polyline collection var inertialPolylineCollection = scene.primitives.get(0); expect(inertialPolylineCollection.length).toEqual(1); expect(inertialPolylineCollection.modelMatrix).not.toEqual(Matrix4.IDENTITY); var inertialLine = inertialPolylineCollection.get(0); expect(inertialLine.show).toEqual(true); scene.mode = SceneMode.SCENE2D; visualizer.update(time); //They'll be one inertial polyline collection (with no visible lines) //and a new fixed polyline collection. expect(scene.primitives.length).toEqual(2); var fixedPolylineCollection = scene.primitives.get(1); expect(inertialLine.show).toEqual(false); expect(fixedPolylineCollection.length).toEqual(1); expect(fixedPolylineCollection.modelMatrix).toEqual(Matrix4.IDENTITY); var fixedLine = fixedPolylineCollection.get(0); expect(fixedLine.show).toEqual(true); }); it('clear hides primitives.', function() { var times = [new JulianDate(0, 0), new JulianDate(1, 0)]; var updateTime = new JulianDate(0.5, 0); var positions = [new Cartesian3(1234, 5678, 9101112), new Cartesian3(5678, 1234, 1101112)]; var entityCollection = new EntityCollection(); visualizer = new PathVisualizer(scene, entityCollection); expect(scene.primitives.length).toEqual(0); var testObject = entityCollection.getOrCreateEntity('test'); var position = new SampledPositionProperty(); testObject.position = position; position.addSamples(times, positions); var path = testObject.path = new PathGraphics(); path.show = new ConstantProperty(true); path.color = new ConstantProperty(new Color(0.8, 0.7, 0.6, 0.5)); path.width = new ConstantProperty(12.5); path.outlineColor = new ConstantProperty(new Color(0.1, 0.2, 0.3, 0.4)); path.outlineWidth = new ConstantProperty(2.5); path.leadTime = new ConstantProperty(25); path.trailTime = new ConstantProperty(10); visualizer.update(updateTime); expect(scene.primitives.length).toEqual(1); var polylineCollection = scene.primitives.get(0); var primitive = polylineCollection.get(0); visualizer.update(updateTime); //Clearing won't actually remove the primitive because of the //internal cache used by the visualizer, instead it just hides it. entityCollection.removeAll(); expect(primitive.show).toEqual(false); expect(primitive.id).toBeUndefined(); }); it('Visualizer sets entity property.', function() { var times = [new JulianDate(0, 0), new JulianDate(1, 0)]; var updateTime = new JulianDate(0.5, 0); var positions = [new Cartesian3(1234, 5678, 9101112), new Cartesian3(5678, 1234, 1101112)]; var entityCollection = new EntityCollection(); visualizer = new PathVisualizer(scene, entityCollection); expect(scene.primitives.length).toEqual(0); var testObject = entityCollection.getOrCreateEntity('test'); var position = new SampledPositionProperty(); testObject.position = position; position.addSamples(times, positions); var path = testObject.path = new PathGraphics(); path.show = new ConstantProperty(true); path.color = new ConstantProperty(new Color(0.8, 0.7, 0.6, 0.5)); path.width = new ConstantProperty(12.5); path.outlineColor = new ConstantProperty(new Color(0.1, 0.2, 0.3, 0.4)); path.outlineWidth = new ConstantProperty(2.5); path.leadTime = new ConstantProperty(25); path.trailTime = new ConstantProperty(10); visualizer.update(updateTime); var polylineCollection = scene.primitives.get(0); var primitive = polylineCollection.get(0); expect(primitive.id).toEqual(testObject); }); it('subSample works for constant properties', function() { var property = new ConstantPositionProperty(new Cartesian3(1000, 2000, 3000)); var start = new JulianDate(0, 0); var stop = new JulianDate(1, 0); var updateTime = new JulianDate(1, 43200); var referenceFrame = ReferenceFrame.FIXED; var maximumStep = 10; var result = PathVisualizer._subSample(property, start, stop, updateTime, referenceFrame, maximumStep); expect(result).toEqual([property._value]); }); it('subSample works for reference properties', function() { var property = new ConstantPositionProperty(new Cartesian3(1000, 2000, 3000)); var start = new JulianDate(0, 0); var stop = new JulianDate(1, 0); var updateTime = new JulianDate(1, 43200); var referenceFrame = ReferenceFrame.FIXED; var maximumStep = 10; var entities = new EntityCollection(); var targetEntity = entities.getOrCreateEntity('target'); targetEntity.position = property; var referenceProperty = new ReferenceProperty(entities, 'target', ['position']); var result = PathVisualizer._subSample(referenceProperty, start, stop, updateTime, referenceFrame, maximumStep); expect(result).toEqual([property._value]); }); it('subSample works for sampled properties', function() { var property = new SampledPositionProperty(); var start = new JulianDate(0, 0); var stop = new JulianDate(1, 0); property.addSample(start, new Cartesian3(0, 0, 0)); property.addSample(stop, new Cartesian3(0, 0, 100)); var updateTime = new JulianDate(0, 43200); var referenceFrame = ReferenceFrame.FIXED; var maximumStep = 86400; var result = []; //A large maximum step causes no sub-smapling. PathVisualizer._subSample(property, start, stop, updateTime, referenceFrame, maximumStep, result); expect(result).toEqual([property.getValue(start), property.getValue(updateTime), property.getValue(stop)]); //An evenly spaced maximum step causes equal steps from start to stop maximumStep = 28800; var expectedStep = 28800; PathVisualizer._subSample(property, start, stop, updateTime, referenceFrame, maximumStep, result); expect(result).toEqual([property.getValue(start), property.getValue(JulianDate.addSeconds(start, expectedStep, new JulianDate())), property.getValue(updateTime), property.getValue(JulianDate.addSeconds(start, expectedStep * 2, new JulianDate())), property.getValue(stop)]); //An maximum step size that is slightly more than halfway between points causes a single step halfway between points maximumStep = 43201; expectedStep = 43200; updateTime = new JulianDate(0, 64800); PathVisualizer._subSample(property, start, stop, updateTime, referenceFrame, maximumStep, result); expect(result).toEqual([property.getValue(start), property.getValue(JulianDate.addSeconds(start, expectedStep, new JulianDate())), property.getValue(updateTime), property.getValue(stop)]); //An maximum step size that is slightly less than halfway between points causes two steps of the eqaul size to be taken between points maximumStep = 43199; expectedStep = 28800; updateTime = new JulianDate(0, 21600); PathVisualizer._subSample(property, start, stop, updateTime, referenceFrame, maximumStep, result); expect(result).toEqual([property.getValue(start), property.getValue(updateTime), property.getValue(JulianDate.addSeconds(start, expectedStep, new JulianDate())), property.getValue(JulianDate.addSeconds(start, expectedStep * 2, new JulianDate())), property.getValue(stop)]); }); it('subSample works for interval properties', function() { var t1 = new JulianDate(0, 0); var t2 = new JulianDate(1, 0); var t3 = new JulianDate(2, 0); var t4 = new JulianDate(3, 0); var property = new TimeIntervalCollectionPositionProperty(); property.intervals.addInterval(new TimeInterval({ start : t1, stop : t2, data : new Cartesian3(0, 0, 1) })); property.intervals.addInterval(new TimeInterval({ start : t2, stop : t3, isStartIncluded : false, isStopIncluded : false, data : new Cartesian3(0, 0, 2) })); property.intervals.addInterval(new TimeInterval({ start : t3, stop : t4, data : new Cartesian3(0, 0, 3) })); var updateTime = new JulianDate(1, 43200); var referenceFrame = ReferenceFrame.FIXED; var maximumStep = 10; var result = []; PathVisualizer._subSample(property, t1, t4, updateTime, referenceFrame, maximumStep, result); expect(result).toEqual([new Cartesian3(0, 0, 1), new Cartesian3(0, 0, 2), new Cartesian3(0, 0, 3)]); PathVisualizer._subSample(property, t2, t3, updateTime, referenceFrame, maximumStep, result); expect(result).toEqual([new Cartesian3(0, 0, 1), new Cartesian3(0, 0, 2), new Cartesian3(0, 0, 3)]); PathVisualizer._subSample(property, t1, t2, updateTime, referenceFrame, maximumStep, result); expect(result).toEqual([new Cartesian3(0, 0, 1)]); PathVisualizer._subSample(property, t3, t4, updateTime, referenceFrame, maximumStep, result); expect(result).toEqual([new Cartesian3(0, 0, 3)]); }); function CustomPositionProperty(innerProperty) { this.SampledProperty = innerProperty; this.isConstant = innerProperty.isConstant; this.definitionChanged = innerProperty.definitionChanged; this.referenceFrame = innerProperty.referenceFrame; this.getValue = function(time, result) { return innerProperty.getValue(time, result); }; this.getValueInReferenceFrame = function(time, referenceFrame, result) { return innerProperty.getValueInReferenceFrame(time, referenceFrame, result); }; this.equals = function(other) { return innerProperty.equals(other); }; } it('subSample works for custom properties', function() { var t1 = new JulianDate(0, 0); var t2 = new JulianDate(1, 0); var t3 = new JulianDate(2, 0); var t4 = new JulianDate(3, 0); var updateTime = new JulianDate(1, 1); var sampledProperty = new SampledPositionProperty(); sampledProperty.addSample(t1, new Cartesian3(0, 0, 1)); sampledProperty.addSample(t2, new Cartesian3(0, 0, 2)); sampledProperty.addSample(t3, new Cartesian3(0, 0, 3)); sampledProperty.addSample(t4, new Cartesian3(0, 0, 4)); var property = new CustomPositionProperty(sampledProperty); var referenceFrame = ReferenceFrame.FIXED; var maximumStep = 43200; var result = []; PathVisualizer._subSample(property, t1, t4, updateTime, referenceFrame, maximumStep, result); expect(result).toEqual([sampledProperty.getValue(t1), sampledProperty.getValue(JulianDate.addSeconds(t1, maximumStep, new JulianDate())), sampledProperty.getValue(JulianDate.addSeconds(t1, maximumStep * 2, new JulianDate())), sampledProperty.getValue(updateTime), sampledProperty.getValue(JulianDate.addSeconds(t1, maximumStep * 3, new JulianDate())), sampledProperty.getValue(JulianDate.addSeconds(t1, maximumStep * 4, new JulianDate())), sampledProperty.getValue(JulianDate.addSeconds(t1, maximumStep * 5, new JulianDate())), sampledProperty.getValue(JulianDate.addSeconds(t1, maximumStep * 6, new JulianDate()))]); }); function createCompositeTest(useReferenceProperty){ var t1 = new JulianDate(0, 0); var t2 = new JulianDate(1, 0); var t3 = new JulianDate(2, 0); var t4 = new JulianDate(3, 0); var t5 = new JulianDate(4, 0); var t6 = new JulianDate(5, 0); var constantProperty = new ConstantPositionProperty(new Cartesian3(0, 0, 1)); var intervalProperty = new TimeIntervalCollectionPositionProperty(); intervalProperty.intervals.addInterval(new TimeInterval({ start : t1, stop : t2, data : new Cartesian3(0, 0, 1) })); intervalProperty.intervals.addInterval(new TimeInterval({ start : t2, stop : t3, isStartIncluded : false, isStopIncluded : false, data : new Cartesian3(0, 0, 2) })); intervalProperty.intervals.addInterval(new TimeInterval({ start : t1, stop : t2, data : new Cartesian3(0, 0, 3) })); var sampledProperty = new SampledPositionProperty(); sampledProperty.addSample(t1, new Cartesian3(0, 0, 1)); sampledProperty.addSample(t2, new Cartesian3(0, 0, 2)); sampledProperty.addSample(t3, new Cartesian3(0, 0, 3)); sampledProperty.addSample(t4, new Cartesian3(0, 0, 4)); var entities = new EntityCollection(); var targetEntity = entities.getOrCreateEntity('target'); targetEntity.position = new ConstantPositionProperty(new Cartesian3(0, 0, 5)); var referenceProperty = new ReferenceProperty(entities, 'target', ['position']); var scaledProperty = new ScaledPositionProperty(referenceProperty); var property = new CompositePositionProperty(); property.intervals.addInterval(new TimeInterval({ start : t1, stop : t2, data : intervalProperty })); property.intervals.addInterval(new TimeInterval({ start : t2, stop : t3, isStartIncluded : false, isStopIncluded : false, data : constantProperty })); property.intervals.addInterval(new TimeInterval({ start : t3, stop : t4, data : sampledProperty })); property.intervals.addInterval(new TimeInterval({ start : t4, stop : t5, isStartIncluded : false, isStopIncluded : true, data : referenceProperty })); property.intervals.addInterval(new TimeInterval({ start : t5, stop : t6, isStartIncluded : false, isStopIncluded : true, data : scaledProperty })); var updateTime = new JulianDate(0, 0); var referenceFrame = ReferenceFrame.FIXED; var maximumStep = 43200; var result = []; var propertyToTest = property; if (useReferenceProperty) { var testReference = entities.getOrCreateEntity('testReference'); testReference.position = property; propertyToTest = new ReferenceProperty(entities, 'testReference', ['position']); } PathVisualizer._subSample(propertyToTest, t1, t6, updateTime, referenceFrame, maximumStep, result); expect(result).toEqual([intervalProperty.intervals.get(0).data, constantProperty.getValue(t1), sampledProperty.getValue(t3), sampledProperty.getValue(JulianDate.addSeconds(t3, maximumStep, new JulianDate())), sampledProperty.getValue(t4), targetEntity.position.getValue(t5), scaledProperty.getValue(t6)]); } it('subSample works for composite properties', function() { createCompositeTest(false); }); it('subSample works for composite properties wrapped in reference properties', function() { createCompositeTest(true); }); }, 'WebGL');