From 885a6ecbb23ee5e052f3e91db5ecf7b0fc0051d8 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Tue, 18 Jun 2013 15:29:05 -0400 Subject: [PATCH 01/20] Start messing around with GeoJSON support. --- Source/DynamicScene/CzmlDataSource.js | 6 +- Source/DynamicScene/GeoJsonDataSource.js | 214 ++++++++++++++++++ Source/DynamicScene/StaticPositionProperty.js | 25 ++ Source/DynamicScene/StaticProperty.js | 41 ++++ 4 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 Source/DynamicScene/GeoJsonDataSource.js create mode 100644 Source/DynamicScene/StaticPositionProperty.js create mode 100644 Source/DynamicScene/StaticProperty.js diff --git a/Source/DynamicScene/CzmlDataSource.js b/Source/DynamicScene/CzmlDataSource.js index 3c7e08033c6c..18442539ba32 100644 --- a/Source/DynamicScene/CzmlDataSource.js +++ b/Source/DynamicScene/CzmlDataSource.js @@ -5,9 +5,9 @@ define(['../Core/ClockRange', '../Core/Event', '../Core/Iso8601', '../Core/loadJson', - '../DynamicScene/DynamicClock', - '../DynamicScene/processCzml', - '../DynamicScene/DynamicObjectCollection' + './DynamicClock', + './processCzml', + './DynamicObjectCollection' ], function( ClockRange, ClockStep, diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js new file mode 100644 index 000000000000..4da1967e48ae --- /dev/null +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -0,0 +1,214 @@ +/*global define*/ +define(['../Core/createGuid', + '../Core/Cartographic', + '../Core/DeveloperError', + '../Core/Ellipsoid', + '../Core/Event', + '../Core/loadJson', + './DynamicLabel', + './DynamicObjectCollection', + './StaticProperty', + './StaticPositionProperty' + ], function( + createGuid, + Cartographic, + DeveloperError, + Ellipsoid, + Event, + loadJson, + DynamicLabel, + DynamicObjectCollection, + StaticProperty, + StaticPositionProperty) { + "use strict"; + + function processFeature(feature, dynamicObjectCollection) { + processors[feature.geometry.type](feature, dynamicObjectCollection); + } + + function processFeatureCollection(featureCollection, dynamicObjectCollection) { + var features = featureCollection.features; + for ( var i = 0, len = features.length; i < len; i++) { + var feature = features[i]; + processors[feature.type](feature, dynamicObjectCollection); + } + } + + function processGeometryCollection(geometryCollection, dynamicObjectCollection) { + var geometries = geometryCollection.geometries; + for ( var i = 0, len = geometries.length; i < len; i++) { + var geometry = geometries[i]; + processors[geometry.type](geometry, dynamicObjectCollection); + } + } + + function processLineString(feature, dynamicObjectCollection) { + var id = feature.id; + if (typeof id === 'undefined') { + id = createGuid(); + } + var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); + dynamicObject.feature = feature; + + var coordinates = feature.geometry.coordinates; + var positions = new Array(coordinates.length); + for ( var i = 0; i < coordinates.length; i++) { + positions[i] = Cartographic.fromDegrees(coordinates[i][0], coordinates[i][1]); + } + dynamicObject.vertexPositions = new StaticPositionProperty(Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions)); + } + + function processMultiLineString(feature, dynamicObjectCollection) { + var lineStrings = feature.geometry.coordinates; + for ( var i = 0; i < lineStrings.length; i++) { + var lineString = lineStrings[i]; + var dynamicObject = dynamicObjectCollection.getOrCreateObject(createGuid()); + dynamicObject.feature = feature; + + for ( var q = 0; q < lineString.length; q++) { + var positions = new Array(lineString.length); + var index = 0; + for ( var z = 0; z < lineString.length; z++) { + positions[index++] = Cartographic.fromDegrees(lineString[i][0], lineString[i][1]); + } + dynamicObject.vertexPositions = new StaticPositionProperty(Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions)); + } + } + } + + function processMultiPoint(feature, dynamicObjectCollection) { + var coordinates = feature.geometry.coordinates; + for ( var i = 0; i < coordinates.length; i++) { + var dynamicObject = dynamicObjectCollection.getOrCreateObject(createGuid()); + dynamicObject.feature = feature; + + var position = Cartographic.fromDegrees(coordinates[i][0], coordinates[i][1]); + dynamicObject.position = new StaticPositionProperty(Ellipsoid.WGS84.cartographicToCartesian(position)); + } + } + + function processMultiPolygon(feature, dynamicObjectCollection) { + var polygons = feature.geometry.coordinates; + for ( var i = 0; i < polygons.length; i++) { + var polygon = polygons[i]; + var dynamicObject = dynamicObjectCollection.getOrCreateObject(createGuid()); + dynamicObject.feature = feature; + + //TODO holes + var vertexPositions = polygon[0]; + for ( var q = 0; q < vertexPositions.length; q++) { + var positions = new Array(vertexPositions.length); + var index = 0; + for ( var z = 0; z < vertexPositions.length; z++) { + positions[index++] = Cartographic.fromDegrees(vertexPositions[i][0], vertexPositions[i][1]); + } + dynamicObject.vertexPositions = new StaticPositionProperty(Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions)); + } + } + } + + function processPoint(feature, dynamicObjectCollection) { + var id = feature.id; + if (typeof id === 'undefined') { + id = createGuid(); + } + var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); + dynamicObject.feature = feature; + + var coordinates = feature.geometry.coordinates; + var position = Cartographic.fromDegrees(coordinates[0], coordinates[1]); + dynamicObject.position = new StaticPositionProperty(Ellipsoid.WGS84.cartographicToCartesian(position)); + + dynamicObject.label = new DynamicLabel(); + dynamicObject.label.text = new StaticProperty("This is GeoJSON!"); + } + + function processPolygon(feature, dynamicObjectCollection) { + var id = feature.id; + if (typeof id === 'undefined') { + id = createGuid(); + } + var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); + dynamicObject.feature = feature; + + //TODO Holes + var coordinates = feature.geometry.coordinates[0]; + var positions = new Array(coordinates.length); + var index = 0; + for ( var i = 0; i < coordinates.length; i++) { + positions[index++] = Cartographic.fromDegrees(coordinates[i][0], coordinates[i][1]); + } + dynamicObject.vertexPositions = new StaticPositionProperty(Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions)); + } + + var processors = { + Feature : processFeature, + FeatureCollection : processFeatureCollection, + GeometryCollection : processGeometryCollection, + LineString : processLineString, + MultiLineString : processMultiLineString, + MultiPoint : processMultiPoint, + MultiPolygon : processMultiPolygon, + Point : processPoint, + Polygon : processPolygon + }; + + var GeoJsonDataSource = function() { + this._changed = new Event(); + this._error = new Event(); + this._dynamicObjectCollection = new DynamicObjectCollection(); + }; + + GeoJsonDataSource.prototype.getChangedEvent = function() { + return this._changed; + }; + + GeoJsonDataSource.prototype.getErrorEvent = function() { + return this._error; + }; + + GeoJsonDataSource.prototype.getClock = function() { + return undefined; + }; + + GeoJsonDataSource.prototype.getDynamicObjectCollection = function() { + return this._dynamicObjectCollection; + }; + + GeoJsonDataSource.prototype.getIsTimeVarying = function() { + return false; + }; + + GeoJsonDataSource.prototype.load = function(geoJson) { + if (typeof geoJson === 'undefined') { + throw new DeveloperError('geoJson is required.'); + } + + this._dynamicObjectCollection.clear(); + + if (typeof geoJson.type === 'undefined') { + throw new DeveloperError('Invalid GeoJSON. Type not defined'); + } + + var callback = processors[geoJson.type]; + if (typeof callback === 'undefined') { + throw new DeveloperError('Unsupported feature type: ' + geoJson.type); + } + callback(geoJson, this._dynamicObjectCollection); + }; + + GeoJsonDataSource.prototype.loadUrl = function(url) { + if (typeof url === 'undefined') { + throw new DeveloperError('url is required.'); + } + + var dataSource = this; + return loadJson(url).then(function(geoJson) { + dataSource.load(geoJson); + }, function(error) { + this._error.raiseEvent(this, error); + }); + }; + + return GeoJsonDataSource; +}); \ No newline at end of file diff --git a/Source/DynamicScene/StaticPositionProperty.js b/Source/DynamicScene/StaticPositionProperty.js new file mode 100644 index 000000000000..73279e171559 --- /dev/null +++ b/Source/DynamicScene/StaticPositionProperty.js @@ -0,0 +1,25 @@ +/*global define*/ +define([ + '../Core/DeveloperError' + ], function( + DeveloperError) { + "use strict"; + + var StaticPositionProperty = function(value) { + this._value = value; + }; + + StaticPositionProperty.prototype.getValueCartesian = function(time, result) { + var value = this._value; + if (typeof value.clone === 'function') { + return value.clone(result); + } + return value; + }; + + StaticPositionProperty.prototype.setValue = function(value) { + this._value = value; + }; + + return StaticPositionProperty; +}); \ No newline at end of file diff --git a/Source/DynamicScene/StaticProperty.js b/Source/DynamicScene/StaticProperty.js new file mode 100644 index 000000000000..aa425a4c53ea --- /dev/null +++ b/Source/DynamicScene/StaticProperty.js @@ -0,0 +1,41 @@ +/*global define*/ +define([ + '../Core/DeveloperError', + '../Core/Iso8601', + '../Core/JulianDate', + '../Core/TimeInterval', + '../Core/TimeIntervalCollection', + '../Core/binarySearch', + '../Core/HermitePolynomialApproximation', + '../Core/LinearApproximation', + '../Core/LagrangePolynomialApproximation' + ], function( + DeveloperError, + Iso8601, + JulianDate, + TimeInterval, + TimeIntervalCollection, + binarySearch, + HermitePolynomialApproximation, + LinearApproximation, + LagrangePolynomialApproximation) { + "use strict"; + + var StaticProperty = function(value) { + this._value = value; + }; + + StaticProperty.prototype.getValue = function(time, result) { + var value = this._value; + if (typeof value.clone === 'function') { + return value.clone(result); + } + return value; + }; + + StaticProperty.prototype.setValue = function(value) { + this._value = value; + }; + + return StaticProperty; +}); \ No newline at end of file From 5ef9066cc968f3406dd1c5fd162feb3a0c17bcf4 Mon Sep 17 00:00:00 2001 From: mramato Date: Tue, 18 Jun 2013 19:10:51 -0400 Subject: [PATCH 02/20] Add drag and drop for .geojson files. --- Source/Widgets/Viewer/viewerDragDropMixin.js | 54 ++++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/Source/Widgets/Viewer/viewerDragDropMixin.js b/Source/Widgets/Viewer/viewerDragDropMixin.js index bc1630d4e6d5..2e1610d3ad17 100644 --- a/Source/Widgets/Viewer/viewerDragDropMixin.js +++ b/Source/Widgets/Viewer/viewerDragDropMixin.js @@ -6,6 +6,7 @@ define([ '../../Core/Event', '../../Core/wrapFunction', '../../DynamicScene/CzmlDataSource', + '../../DynamicScene/GeoJsonDataSource', '../getElement' ], function( defaultValue, @@ -14,8 +15,10 @@ define([ Event, wrapFunction, CzmlDataSource, + GeoJsonDataSource, getElement) { "use strict"; + /*global console*/ /** * A mixin which adds default drag and drop support for CZML files to the Viewer widget. @@ -191,26 +194,45 @@ define([ dropTarget.addEventListener('dragexit', stop, false); } + function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; + } + function createOnLoadCallback(viewer, source, firstTime) { - return function(evt) { - var czmlSource = new CzmlDataSource(); - try { - czmlSource.load(JSON.parse(evt.target.result), source); - viewer.dataSources.add(czmlSource); - if (firstTime) { - var dataClock = czmlSource.getClock(); - if (typeof dataClock !== 'undefined') { - dataClock.clone(viewer.clock); - if (typeof viewer.timeline !== 'undefined') { - viewer.timeline.updateFromClock(); - viewer.timeline.zoomTo(dataClock.startTime, dataClock.stopTime); + if (endsWith(source, "czml")) { + return function(evt) { + var czmlSource = new CzmlDataSource(); + try { + czmlSource.load(JSON.parse(evt.target.result), source); + viewer.dataSources.add(czmlSource); + if (firstTime) { + var dataClock = czmlSource.getClock(); + if (typeof dataClock !== 'undefined') { + dataClock.clone(viewer.clock); + if (typeof viewer.timeline !== 'undefined') { + viewer.timeline.updateFromClock(); + viewer.timeline.zoomTo(dataClock.startTime, dataClock.stopTime); + } } } + } catch (error) { + viewer.onDropError.raiseEvent(viewer, source, error); } - } catch (error) { - viewer.onDropError.raiseEvent(viewer, source, error); - } - }; + }; + } else if (endsWith(source, "geojson")) { + return function(evt) { + var geoJsonSource = new GeoJsonDataSource(); + try { + geoJsonSource.load(JSON.parse(evt.target.result), source); + viewer.dataSources.add(geoJsonSource); + } catch (error) { + viewer.onDropError.raiseEvent(viewer, source, error); + } + }; + } else { + console.log('Unrecognized file extension: ' + source); + window.alert('Unrecognized file extension: ' + source); + } } function createOnDropErrorCallback(viewer, name) { From 5bcd7225d1ce7c92cd32774a74749f57468e304e Mon Sep 17 00:00:00 2001 From: mramato Date: Tue, 18 Jun 2013 20:05:57 -0400 Subject: [PATCH 03/20] Apply default styles to all GeoJSON features. --- Source/DynamicScene/GeoJsonDataSource.js | 63 ++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index 4da1967e48ae..35a137b27fb3 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -1,27 +1,77 @@ /*global define*/ define(['../Core/createGuid', '../Core/Cartographic', + '../Core/Color', '../Core/DeveloperError', '../Core/Ellipsoid', '../Core/Event', '../Core/loadJson', - './DynamicLabel', + './DynamicPoint', + './DynamicPolyline', + './DynamicPolygon', + './DynamicMaterialProperty', './DynamicObjectCollection', './StaticProperty', './StaticPositionProperty' ], function( createGuid, Cartographic, + Color, DeveloperError, Ellipsoid, Event, loadJson, - DynamicLabel, + DynamicPoint, + DynamicPolyline, + DynamicPolygon, + DynamicMaterialProperty, DynamicObjectCollection, StaticProperty, StaticPositionProperty) { "use strict"; + function applyPointDefaults(dynamicObject) { + var point = new DynamicPoint(); + point.color = new StaticProperty(Color.BLUE); + point.pixelSize = new StaticProperty(10); + point.outlineColor = new StaticProperty(Color.BLACK); + point.outlineWidth = new StaticProperty(1); + dynamicObject.point = point; + } + + function applyPolylineDefaults(dynamicObject) { + var polyline = new DynamicPolyline(); + polyline.color = new StaticProperty(Color.BLUE); + polyline.width = new StaticProperty(2); + polyline.outlineColor = new StaticProperty(Color.BLACK); + polyline.outlineWidth = new StaticProperty(1); + dynamicObject.polyline = polyline; + } + + var staticColorCzml = { + solidColor : { + color : { + rgba : [0, 0, 255, 25.5] + } + } + }; + + var material = new DynamicMaterialProperty(); + material.processCzmlIntervals(staticColorCzml, undefined, undefined); + + function applyPolygonDefaults(dynamicObject) { + var polyline = new DynamicPolyline(); + polyline.color = new StaticProperty(Color.BLUE); + polyline.width = new StaticProperty(1); + polyline.outlineColor = new StaticProperty(Color.BLACK); + polyline.outlineWidth = new StaticProperty(0); + dynamicObject.polyline = polyline; + + var polygon = new DynamicPolygon(); + polygon.material = material; + dynamicObject.polygon = polygon; + } + function processFeature(feature, dynamicObjectCollection) { processors[feature.geometry.type](feature, dynamicObjectCollection); } @@ -56,6 +106,7 @@ define(['../Core/createGuid', positions[i] = Cartographic.fromDegrees(coordinates[i][0], coordinates[i][1]); } dynamicObject.vertexPositions = new StaticPositionProperty(Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions)); + applyPolylineDefaults(dynamicObject); } function processMultiLineString(feature, dynamicObjectCollection) { @@ -72,6 +123,7 @@ define(['../Core/createGuid', positions[index++] = Cartographic.fromDegrees(lineString[i][0], lineString[i][1]); } dynamicObject.vertexPositions = new StaticPositionProperty(Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions)); + applyPolylineDefaults(dynamicObject); } } } @@ -84,6 +136,7 @@ define(['../Core/createGuid', var position = Cartographic.fromDegrees(coordinates[i][0], coordinates[i][1]); dynamicObject.position = new StaticPositionProperty(Ellipsoid.WGS84.cartographicToCartesian(position)); + applyPointDefaults(dynamicObject); } } @@ -103,6 +156,7 @@ define(['../Core/createGuid', positions[index++] = Cartographic.fromDegrees(vertexPositions[i][0], vertexPositions[i][1]); } dynamicObject.vertexPositions = new StaticPositionProperty(Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions)); + applyPolygonDefaults(dynamicObject); } } } @@ -118,9 +172,7 @@ define(['../Core/createGuid', var coordinates = feature.geometry.coordinates; var position = Cartographic.fromDegrees(coordinates[0], coordinates[1]); dynamicObject.position = new StaticPositionProperty(Ellipsoid.WGS84.cartographicToCartesian(position)); - - dynamicObject.label = new DynamicLabel(); - dynamicObject.label.text = new StaticProperty("This is GeoJSON!"); + applyPointDefaults(dynamicObject); } function processPolygon(feature, dynamicObjectCollection) { @@ -139,6 +191,7 @@ define(['../Core/createGuid', positions[index++] = Cartographic.fromDegrees(coordinates[i][0], coordinates[i][1]); } dynamicObject.vertexPositions = new StaticPositionProperty(Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions)); + applyPolygonDefaults(dynamicObject); } var processors = { From e11f9ecc98d20c0e3816f471f7f09ae90039264f Mon Sep 17 00:00:00 2001 From: mramato Date: Tue, 18 Jun 2013 22:25:23 -0400 Subject: [PATCH 04/20] Ongoing GeoJSON prototyping. --- Source/DynamicScene/GeoJsonDataSource.js | 195 +++++++++++------------ Source/Scene/CameraController.js | 2 +- 2 files changed, 97 insertions(+), 100 deletions(-) diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index 35a137b27fb3..488f14515838 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -30,6 +30,72 @@ define(['../Core/createGuid', StaticPositionProperty) { "use strict"; + var GeoJsonDataSource = function() { + this._changed = new Event(); + this._error = new Event(); + this._dynamicObjectCollection = new DynamicObjectCollection(); + }; + + GeoJsonDataSource.prototype.getChangedEvent = function() { + return this._changed; + }; + + GeoJsonDataSource.prototype.getErrorEvent = function() { + return this._error; + }; + + GeoJsonDataSource.prototype.getClock = function() { + return undefined; + }; + + GeoJsonDataSource.prototype.getDynamicObjectCollection = function() { + return this._dynamicObjectCollection; + }; + + GeoJsonDataSource.prototype.getIsTimeVarying = function() { + return false; + }; + + GeoJsonDataSource.prototype.load = function(geoJson, source) { + if (typeof geoJson === 'undefined') { + throw new DeveloperError('geoJson is required.'); + } + + this._dynamicObjectCollection.clear(); + + if (typeof geoJson.type === 'undefined') { + throw new DeveloperError('Invalid GeoJSON. Type not defined'); + } + + var callback = types[geoJson.type]; + if (typeof callback === 'undefined') { + throw new DeveloperError('Unsupported feature type: ' + geoJson.type); + } + var crsFunction = defaultCrsFunction; + if (typeof geoJson.crs !== 'undefined') { + //TODO look up CRS transformation. + } + callback(geoJson, this._dynamicObjectCollection, crsFunction, source); + }; + + GeoJsonDataSource.prototype.loadUrl = function(url) { + if (typeof url === 'undefined') { + throw new DeveloperError('url is required.'); + } + + var dataSource = this; + return loadJson(url).then(function(geoJson) { + dataSource.load(geoJson, url); + }, function(error) { + this._error.raiseEvent(this, error); + }); + }; + + function defaultCrsFunction(coordinates) { + var cartographic = Cartographic.fromDegrees(coordinates[0], coordinates[1], coordinates[2]); + return Ellipsoid.WGS84.cartographicToCartesian(cartographic); + } + function applyPointDefaults(dynamicObject) { var point = new DynamicPoint(); point.color = new StaticProperty(Color.BLUE); @@ -48,16 +114,14 @@ define(['../Core/createGuid', dynamicObject.polyline = polyline; } - var staticColorCzml = { + var polygonMaterial = new DynamicMaterialProperty(); + polygonMaterial.processCzmlIntervals({ solidColor : { color : { rgba : [0, 0, 255, 25.5] } } - }; - - var material = new DynamicMaterialProperty(); - material.processCzmlIntervals(staticColorCzml, undefined, undefined); + }, undefined, undefined); function applyPolygonDefaults(dynamicObject) { var polyline = new DynamicPolyline(); @@ -68,31 +132,31 @@ define(['../Core/createGuid', dynamicObject.polyline = polyline; var polygon = new DynamicPolygon(); - polygon.material = material; + polygon.material = polygonMaterial; dynamicObject.polygon = polygon; } - function processFeature(feature, dynamicObjectCollection) { - processors[feature.geometry.type](feature, dynamicObjectCollection); + function processFeature(feature, dynamicObjectCollection, crsFunction, source) { + types[feature.geometry.type](feature, dynamicObjectCollection, crsFunction, source); } - function processFeatureCollection(featureCollection, dynamicObjectCollection) { + function processFeatureCollection(featureCollection, dynamicObjectCollection, crsFunction, source) { var features = featureCollection.features; for ( var i = 0, len = features.length; i < len; i++) { var feature = features[i]; - processors[feature.type](feature, dynamicObjectCollection); + types[feature.type](feature, dynamicObjectCollection, crsFunction, source); } } - function processGeometryCollection(geometryCollection, dynamicObjectCollection) { + function processGeometryCollection(geometryCollection, dynamicObjectCollection, crsFunction, source) { var geometries = geometryCollection.geometries; for ( var i = 0, len = geometries.length; i < len; i++) { var geometry = geometries[i]; - processors[geometry.type](geometry, dynamicObjectCollection); + types[geometry.type](geometry, dynamicObjectCollection, crsFunction, source); } } - function processLineString(feature, dynamicObjectCollection) { + function processLineString(feature, dynamicObjectCollection, crsFunction, source) { var id = feature.id; if (typeof id === 'undefined') { id = createGuid(); @@ -103,44 +167,38 @@ define(['../Core/createGuid', var coordinates = feature.geometry.coordinates; var positions = new Array(coordinates.length); for ( var i = 0; i < coordinates.length; i++) { - positions[i] = Cartographic.fromDegrees(coordinates[i][0], coordinates[i][1]); + positions[i] = crsFunction(coordinates[i]); } - dynamicObject.vertexPositions = new StaticPositionProperty(Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions)); + dynamicObject.vertexPositions = new StaticPositionProperty(positions); applyPolylineDefaults(dynamicObject); } - function processMultiLineString(feature, dynamicObjectCollection) { + function processMultiLineString(feature, dynamicObjectCollection, crsFunction, source) { var lineStrings = feature.geometry.coordinates; for ( var i = 0; i < lineStrings.length; i++) { var lineString = lineStrings[i]; var dynamicObject = dynamicObjectCollection.getOrCreateObject(createGuid()); dynamicObject.feature = feature; - - for ( var q = 0; q < lineString.length; q++) { - var positions = new Array(lineString.length); - var index = 0; - for ( var z = 0; z < lineString.length; z++) { - positions[index++] = Cartographic.fromDegrees(lineString[i][0], lineString[i][1]); - } - dynamicObject.vertexPositions = new StaticPositionProperty(Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions)); - applyPolylineDefaults(dynamicObject); + var positions = new Array(lineString.length); + for ( var z = 0; z < lineString.length; z++) { + positions[z] = crsFunction(lineString[z]); } + dynamicObject.vertexPositions = new StaticPositionProperty(positions); + applyPolylineDefaults(dynamicObject); } } - function processMultiPoint(feature, dynamicObjectCollection) { + function processMultiPoint(feature, dynamicObjectCollection, crsFunction, source) { var coordinates = feature.geometry.coordinates; for ( var i = 0; i < coordinates.length; i++) { var dynamicObject = dynamicObjectCollection.getOrCreateObject(createGuid()); dynamicObject.feature = feature; - - var position = Cartographic.fromDegrees(coordinates[i][0], coordinates[i][1]); - dynamicObject.position = new StaticPositionProperty(Ellipsoid.WGS84.cartographicToCartesian(position)); + dynamicObject.position = new StaticPositionProperty(crsFunction(coordinates[i])); applyPointDefaults(dynamicObject); } } - function processMultiPolygon(feature, dynamicObjectCollection) { + function processMultiPolygon(feature, dynamicObjectCollection, crsFunction, source) { var polygons = feature.geometry.coordinates; for ( var i = 0; i < polygons.length; i++) { var polygon = polygons[i]; @@ -151,17 +209,16 @@ define(['../Core/createGuid', var vertexPositions = polygon[0]; for ( var q = 0; q < vertexPositions.length; q++) { var positions = new Array(vertexPositions.length); - var index = 0; for ( var z = 0; z < vertexPositions.length; z++) { - positions[index++] = Cartographic.fromDegrees(vertexPositions[i][0], vertexPositions[i][1]); + positions[z] = crsFunction(vertexPositions[z]); } - dynamicObject.vertexPositions = new StaticPositionProperty(Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions)); + dynamicObject.vertexPositions = new StaticPositionProperty(positions); applyPolygonDefaults(dynamicObject); } } } - function processPoint(feature, dynamicObjectCollection) { + function processPoint(feature, dynamicObjectCollection, crsFunction, source) { var id = feature.id; if (typeof id === 'undefined') { id = createGuid(); @@ -169,13 +226,11 @@ define(['../Core/createGuid', var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); dynamicObject.feature = feature; - var coordinates = feature.geometry.coordinates; - var position = Cartographic.fromDegrees(coordinates[0], coordinates[1]); - dynamicObject.position = new StaticPositionProperty(Ellipsoid.WGS84.cartographicToCartesian(position)); + dynamicObject.position = new StaticPositionProperty(crsFunction(feature.geometry.coordinates)); applyPointDefaults(dynamicObject); } - function processPolygon(feature, dynamicObjectCollection) { + function processPolygon(feature, dynamicObjectCollection, crsFunction, source) { var id = feature.id; if (typeof id === 'undefined') { id = createGuid(); @@ -186,15 +241,14 @@ define(['../Core/createGuid', //TODO Holes var coordinates = feature.geometry.coordinates[0]; var positions = new Array(coordinates.length); - var index = 0; for ( var i = 0; i < coordinates.length; i++) { - positions[index++] = Cartographic.fromDegrees(coordinates[i][0], coordinates[i][1]); + positions[i] = crsFunction(coordinates[i]); } - dynamicObject.vertexPositions = new StaticPositionProperty(Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions)); + dynamicObject.vertexPositions = new StaticPositionProperty(positions); applyPolygonDefaults(dynamicObject); } - var processors = { + var types = { Feature : processFeature, FeatureCollection : processFeatureCollection, GeometryCollection : processGeometryCollection, @@ -206,62 +260,5 @@ define(['../Core/createGuid', Polygon : processPolygon }; - var GeoJsonDataSource = function() { - this._changed = new Event(); - this._error = new Event(); - this._dynamicObjectCollection = new DynamicObjectCollection(); - }; - - GeoJsonDataSource.prototype.getChangedEvent = function() { - return this._changed; - }; - - GeoJsonDataSource.prototype.getErrorEvent = function() { - return this._error; - }; - - GeoJsonDataSource.prototype.getClock = function() { - return undefined; - }; - - GeoJsonDataSource.prototype.getDynamicObjectCollection = function() { - return this._dynamicObjectCollection; - }; - - GeoJsonDataSource.prototype.getIsTimeVarying = function() { - return false; - }; - - GeoJsonDataSource.prototype.load = function(geoJson) { - if (typeof geoJson === 'undefined') { - throw new DeveloperError('geoJson is required.'); - } - - this._dynamicObjectCollection.clear(); - - if (typeof geoJson.type === 'undefined') { - throw new DeveloperError('Invalid GeoJSON. Type not defined'); - } - - var callback = processors[geoJson.type]; - if (typeof callback === 'undefined') { - throw new DeveloperError('Unsupported feature type: ' + geoJson.type); - } - callback(geoJson, this._dynamicObjectCollection); - }; - - GeoJsonDataSource.prototype.loadUrl = function(url) { - if (typeof url === 'undefined') { - throw new DeveloperError('url is required.'); - } - - var dataSource = this; - return loadJson(url).then(function(geoJson) { - dataSource.load(geoJson); - }, function(error) { - this._error.raiseEvent(this, error); - }); - }; - return GeoJsonDataSource; }); \ No newline at end of file diff --git a/Source/Scene/CameraController.js b/Source/Scene/CameraController.js index 7ce3d00eb374..0265149ff478 100644 --- a/Source/Scene/CameraController.js +++ b/Source/Scene/CameraController.js @@ -947,7 +947,7 @@ define([ return result; } /** - * Get the camera position neede to view an extent on an ellipsoid or map + * Get the camera position needed to view an extent on an ellipsoid or map * @memberof CameraController * * @param {Extent} extent The extent to view. From 7c4dc49eda4c86eef79de55f0a60d3cd4f91edf1 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Wed, 19 Jun 2013 12:04:27 -0400 Subject: [PATCH 05/20] Start of some specs, still a ton of work to do. --- Source/DynamicScene/GeoJsonDataSource.js | 129 +++++++++++++--- Specs/DynamicScene/GeoJsonDataSourceSpec.js | 154 ++++++++++++++++++++ 2 files changed, 261 insertions(+), 22 deletions(-) create mode 100644 Specs/DynamicScene/GeoJsonDataSourceSpec.js diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index 488f14515838..881388db3a1d 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -2,6 +2,7 @@ define(['../Core/createGuid', '../Core/Cartographic', '../Core/Color', + '../Core/defineProperties', '../Core/DeveloperError', '../Core/Ellipsoid', '../Core/Event', @@ -12,11 +13,12 @@ define(['../Core/createGuid', './DynamicMaterialProperty', './DynamicObjectCollection', './StaticProperty', - './StaticPositionProperty' - ], function( + './StaticPositionProperty', + '../ThirdParty/when'], function( createGuid, Cartographic, Color, + defineProperties, DeveloperError, Ellipsoid, Event, @@ -27,15 +29,27 @@ define(['../Core/createGuid', DynamicMaterialProperty, DynamicObjectCollection, StaticProperty, - StaticPositionProperty) { - "use strict"; + StaticPositionProperty, + when) { + "use strict"; var GeoJsonDataSource = function() { this._changed = new Event(); this._error = new Event(); this._dynamicObjectCollection = new DynamicObjectCollection(); + this._crsNameHandlers = {}; + this._crsLinkTypeHandlers = {}; + this._crsLinkHrefHandlers = {}; }; + defineProperties(GeoJsonDataSource.prototype, { + crsTransformations : { + get : function() { + return this._crsNameHandlers; + } + } + }); + GeoJsonDataSource.prototype.getChangedEvent = function() { return this._changed; }; @@ -61,21 +75,70 @@ define(['../Core/createGuid', throw new DeveloperError('geoJson is required.'); } - this._dynamicObjectCollection.clear(); - if (typeof geoJson.type === 'undefined') { throw new DeveloperError('Invalid GeoJSON. Type not defined'); } - var callback = types[geoJson.type]; - if (typeof callback === 'undefined') { - throw new DeveloperError('Unsupported feature type: ' + geoJson.type); + var typeHandler = types[geoJson.type]; + if (typeof typeHandler === 'undefined') { + throw new DeveloperError('Unsupported GeoJSON object type: ' + geoJson.type); } - var crsFunction = defaultCrsFunction; - if (typeof geoJson.crs !== 'undefined') { - //TODO look up CRS transformation. + + //Check for a custom Coordinate Reference System. + var crsPromise; + var crs = geoJson.crs; + if (typeof crs !== 'undefined') { + if (crs === null) { + throw new DeveloperError('crs is null.'); + } + if (typeof crs.type === 'undefined') { + throw new DeveloperError('Invalid crs property, crs.type is undefined.'); + } else if (typeof crs.properties === 'undefined') { + throw new DeveloperError('Invalid crs property, crs.properties is undefined.'); + } + + var properties = crs.properties; + if (crs.type === 'name') { + var crsFunction = this._crsNameHandlers[properties.name]; + if (typeof crsFunction === 'undefined') { + throw new DeveloperError('Unknown crs name: ' + properties.name); + } + + crsPromise = when(crsFunction, function(crsFunction) { + var deferred = when.defer(); + deferred.resolve(crsFunction); + return deferred.promise; + }); + } else if (crs.type === 'link') { + var handler = this._crsLinkHrefHandlers[properties.href]; + if (typeof handler === 'undefined') { + handler = this._crsLinkHandlers; + } + + if (typeof handler === 'undefined') { + throw new DeveloperError('Unable to resolve crs link: ' + properties); + } + + crsPromise = handler(properties.href, properties.type); + } else { + throw new DeveloperError('Unknown crs type: ' + crs.type); + } + } else { + //Use the default + crsPromise = when(defaultCrsFunction, function(defaultCrsFunction) { + var deferred = when.defer(); + deferred.resolve(defaultCrsFunction); + return deferred.promise; + }); } - callback(geoJson, this._dynamicObjectCollection, crsFunction, source); + + this._dynamicObjectCollection.clear(); + + var that = this; + return crsPromise.then(function(crsFunction) { + typeHandler(geoJson, that._dynamicObjectCollection, crsFunction, source); + that._changed.raiseEvent(that); + }); }; GeoJsonDataSource.prototype.loadUrl = function(url) { @@ -137,14 +200,36 @@ define(['../Core/createGuid', } function processFeature(feature, dynamicObjectCollection, crsFunction, source) { - types[feature.geometry.type](feature, dynamicObjectCollection, crsFunction, source); + if (typeof feature.geometry !== 'object') { + throw new DeveloperError('feature.geometry is required to be an object.'); + } + + if (typeof feature.properties !== 'object') { + throw new DeveloperError('feature.properties is required to be an object.'); + } + + if (feature.geometry === null) { + //Null geometry is allowed, so just create an empty dynamicObject instance for it. + var id = feature.id; + if (typeof id === 'undefined') { + id = createGuid(); + } + var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); + dynamicObject.geoJson = feature; + } else { + var geometryType = feature.geometry.type; + var geometryHandler = types[geometryType]; + if (typeof geometryHandler === 'undefined') { + throw new DeveloperError('Unknown geometry type: ' + geometryType); + } + geometryHandler(feature, dynamicObjectCollection, crsFunction, source); + } } function processFeatureCollection(featureCollection, dynamicObjectCollection, crsFunction, source) { var features = featureCollection.features; for ( var i = 0, len = features.length; i < len; i++) { - var feature = features[i]; - types[feature.type](feature, dynamicObjectCollection, crsFunction, source); + processFeature(features[i], dynamicObjectCollection, crsFunction, source); } } @@ -162,7 +247,7 @@ define(['../Core/createGuid', id = createGuid(); } var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); - dynamicObject.feature = feature; + dynamicObject.geoJson = feature; var coordinates = feature.geometry.coordinates; var positions = new Array(coordinates.length); @@ -178,7 +263,7 @@ define(['../Core/createGuid', for ( var i = 0; i < lineStrings.length; i++) { var lineString = lineStrings[i]; var dynamicObject = dynamicObjectCollection.getOrCreateObject(createGuid()); - dynamicObject.feature = feature; + dynamicObject.geoJson = feature; var positions = new Array(lineString.length); for ( var z = 0; z < lineString.length; z++) { positions[z] = crsFunction(lineString[z]); @@ -192,7 +277,7 @@ define(['../Core/createGuid', var coordinates = feature.geometry.coordinates; for ( var i = 0; i < coordinates.length; i++) { var dynamicObject = dynamicObjectCollection.getOrCreateObject(createGuid()); - dynamicObject.feature = feature; + dynamicObject.geoJson = feature; dynamicObject.position = new StaticPositionProperty(crsFunction(coordinates[i])); applyPointDefaults(dynamicObject); } @@ -203,7 +288,7 @@ define(['../Core/createGuid', for ( var i = 0; i < polygons.length; i++) { var polygon = polygons[i]; var dynamicObject = dynamicObjectCollection.getOrCreateObject(createGuid()); - dynamicObject.feature = feature; + dynamicObject.geoJson = feature; //TODO holes var vertexPositions = polygon[0]; @@ -224,7 +309,7 @@ define(['../Core/createGuid', id = createGuid(); } var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); - dynamicObject.feature = feature; + dynamicObject.geoJson = feature; dynamicObject.position = new StaticPositionProperty(crsFunction(feature.geometry.coordinates)); applyPointDefaults(dynamicObject); @@ -236,7 +321,7 @@ define(['../Core/createGuid', id = createGuid(); } var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); - dynamicObject.feature = feature; + dynamicObject.geoJson = feature; //TODO Holes var coordinates = feature.geometry.coordinates[0]; diff --git a/Specs/DynamicScene/GeoJsonDataSourceSpec.js b/Specs/DynamicScene/GeoJsonDataSourceSpec.js new file mode 100644 index 000000000000..a4ca8ea77e45 --- /dev/null +++ b/Specs/DynamicScene/GeoJsonDataSourceSpec.js @@ -0,0 +1,154 @@ +/*global defineSuite*/ +defineSuite(['DynamicScene/GeoJsonDataSource', + 'DynamicScene/DynamicObjectCollection', + 'Core/Cartographic', + 'Core/Ellipsoid', + 'Core/Event' + ], function( + GeoJsonDataSource, + DynamicObjectCollection, + Cartographic, + Ellipsoid, + Event) { + "use strict"; + /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ + + var properties = { + testProperty : 1 + }; + + var pointCoordinates = [102.0, 0.5]; + var pointCoordinatesCartesian = Ellipsoid.WGS84.cartographicToCartesian(Cartographic.fromDegrees(pointCoordinates[0], pointCoordinates[1])); + + var pointFeature = { + type : "Feature", + geometry : { + type : "Point", + coordinates : pointCoordinates + }, + properties : properties + }; + + var featureUndefinedProperties = { + type : "Feature", + geometry : { + type : "Point", + coordinates : pointCoordinates + } + }; + + var featureUndefinedGeometry = { + type : "Feature", + properties : properties + }; + + var featureNullGeometry = { + type : "Feature", + geometry : null, + properties : properties + }; + + var featureUnknownGeometry = { + type : "Feature", + geometry : { + type : "Singularity", + coordinates : pointCoordinates + }, + properties : properties + }; + + it('default constructor has expected values', function() { + var dataSource = new GeoJsonDataSource(); + expect(dataSource.getChangedEvent()).toBeInstanceOf(Event); + expect(dataSource.getErrorEvent()).toBeInstanceOf(Event); + expect(dataSource.getClock()).toBeUndefined(); + expect(dataSource.getDynamicObjectCollection()).toBeInstanceOf(DynamicObjectCollection); + expect(dataSource.getDynamicObjectCollection().getObjects().length).toEqual(0); + expect(dataSource.getIsTimeVarying()).toEqual(false); + }); + + it('Works with null geomtry', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.load(featureNullGeometry); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === 1; + }); + runs(function() { + var pointObject = dynamicObjectCollection.getObjects()[0]; + expect(pointObject.geoJson).toBe(featureNullGeometry); + expect(pointObject.position).toBeUndefined(); + }); + }); + + it('Works with point geomtry', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.load(pointFeature); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === 1; + }); + runs(function() { + var pointObject = dynamicObjectCollection.getObjects()[0]; + expect(pointObject.geoJson).toBe(pointFeature); + expect(pointObject.position).toBeDefined(); + expect(pointObject.position.getValueCartesian()).toEqual(pointCoordinatesCartesian); + expect(pointObject.point).toBeDefined(); + }); + }); + + it('Fails when encountering unknown geomtry', function() { + var dataSource = new GeoJsonDataSource(); + + var failed = false; + dataSource.load(featureUnknownGeometry).then(undefined, function(e) { + failed = true; + }); + + waitsFor(function() { + return failed; + }); + }); + + it('Fails with undefined properties', function() { + var dataSource = new GeoJsonDataSource(); + + var failed = false; + dataSource.load(featureUndefinedProperties).then(undefined, function(e) { + failed = true; + }); + + waitsFor(function() { + return failed; + }); + }); + + it('Fails with undefined geomeetry', function() { + var dataSource = new GeoJsonDataSource(); + + var failed = false; + dataSource.load(featureUndefinedGeometry).then(undefined, function(e) { + failed = true; + }); + + waitsFor(function() { + return failed; + }); + }); + + it('load throws with undefined geojson', function() { + var dataSource = new GeoJsonDataSource(); + expect(function() { + dataSource.load(undefined); + }).toThrow(); + }); + + it('loadUrl throws with undefined Url', function() { + var dataSource = new GeoJsonDataSource(); + expect(function() { + dataSource.loadUrl(undefined); + }).toThrow(); + }); +}); From bc367847450825232cb36bd31e6fb7f238dba629 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Wed, 19 Jun 2013 23:14:03 -0400 Subject: [PATCH 06/20] Ongoing GeoJSON work. --- Source/DynamicScene/GeoJsonDataSource.js | 166 ++++---- Source/Widgets/Viewer/viewerDragDropMixin.js | 9 +- Specs/Data/test.geojson | 40 ++ Specs/DynamicScene/GeoJsonDataSourceSpec.js | 408 +++++++++++++++++-- 4 files changed, 503 insertions(+), 120 deletions(-) create mode 100644 Specs/Data/test.geojson diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index 881388db3a1d..74d7468a2863 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -7,6 +7,7 @@ define(['../Core/createGuid', '../Core/Ellipsoid', '../Core/Event', '../Core/loadJson', + '../Scene/WebMercatorProjection', './DynamicPoint', './DynamicPolyline', './DynamicPolygon', @@ -23,6 +24,7 @@ define(['../Core/createGuid', Ellipsoid, Event, loadJson, + WebMercatorProjection, DynamicPoint, DynamicPolyline, DynamicPolygon, @@ -33,11 +35,27 @@ define(['../Core/createGuid', when) { "use strict"; + var webMercator = new WebMercatorProjection(Ellipsoid.WGS84); + + function webMercatorCrsFunction(coordinates) { + var cartographic = Cartographic.fromDegrees(coordinates[0], coordinates[1], coordinates[2]); + return webMercator.project(cartographic); + } + + function defaultCrsFunction(coordinates) { + var cartographic = Cartographic.fromDegrees(coordinates[0], coordinates[1], coordinates[2]); + return Ellipsoid.WGS84.cartographicToCartesian(cartographic); + } + var GeoJsonDataSource = function() { this._changed = new Event(); this._error = new Event(); this._dynamicObjectCollection = new DynamicObjectCollection(); - this._crsNameHandlers = {}; + this._crsNameHandlers = { + 'urn:ogc:def:crs:OGC:1.3:CRS84' : defaultCrsFunction, + 'EPSG:4326' : defaultCrsFunction, + 'EPSG:3857' : webMercatorCrsFunction + }; this._crsLinkTypeHandlers = {}; this._crsLinkHrefHandlers = {}; }; @@ -70,30 +88,37 @@ define(['../Core/createGuid', return false; }; + GeoJsonDataSource.prototype.loadUrl = function(url) { + if (typeof url === 'undefined') { + throw new DeveloperError('url is required.'); + } + + var dataSource = this; + return loadJson(url).then(function(geoJson) { + return dataSource.load(geoJson, url); + }, function(error) { + dataSource._error.raiseEvent(dataSource, error); + }); + }; + GeoJsonDataSource.prototype.load = function(geoJson, source) { if (typeof geoJson === 'undefined') { throw new DeveloperError('geoJson is required.'); } - if (typeof geoJson.type === 'undefined') { - throw new DeveloperError('Invalid GeoJSON. Type not defined'); - } - - var typeHandler = types[geoJson.type]; + var typeHandler = geoJsonObjectTypes[geoJson.type]; if (typeof typeHandler === 'undefined') { throw new DeveloperError('Unsupported GeoJSON object type: ' + geoJson.type); } - //Check for a custom Coordinate Reference System. + //Check for a Coordinate Reference System. var crsPromise; var crs = geoJson.crs; if (typeof crs !== 'undefined') { if (crs === null) { throw new DeveloperError('crs is null.'); } - if (typeof crs.type === 'undefined') { - throw new DeveloperError('Invalid crs property, crs.type is undefined.'); - } else if (typeof crs.properties === 'undefined') { + if (typeof crs.properties === 'undefined') { throw new DeveloperError('Invalid crs property, crs.properties is undefined.'); } @@ -112,7 +137,7 @@ define(['../Core/createGuid', } else if (crs.type === 'link') { var handler = this._crsLinkHrefHandlers[properties.href]; if (typeof handler === 'undefined') { - handler = this._crsLinkHandlers; + handler = this._crsLinkTypeHandlers[properties.type]; } if (typeof handler === 'undefined') { @@ -136,29 +161,11 @@ define(['../Core/createGuid', var that = this; return crsPromise.then(function(crsFunction) { - typeHandler(geoJson, that._dynamicObjectCollection, crsFunction, source); + typeHandler(geoJson, geoJson, that._dynamicObjectCollection, crsFunction, source); that._changed.raiseEvent(that); }); }; - GeoJsonDataSource.prototype.loadUrl = function(url) { - if (typeof url === 'undefined') { - throw new DeveloperError('url is required.'); - } - - var dataSource = this; - return loadJson(url).then(function(geoJson) { - dataSource.load(geoJson, url); - }, function(error) { - this._error.raiseEvent(this, error); - }); - }; - - function defaultCrsFunction(coordinates) { - var cartographic = Cartographic.fromDegrees(coordinates[0], coordinates[1], coordinates[2]); - return Ellipsoid.WGS84.cartographicToCartesian(cartographic); - } - function applyPointDefaults(dynamicObject) { var point = new DynamicPoint(); point.color = new StaticProperty(Color.BLUE); @@ -199,13 +206,9 @@ define(['../Core/createGuid', dynamicObject.polygon = polygon; } - function processFeature(feature, dynamicObjectCollection, crsFunction, source) { - if (typeof feature.geometry !== 'object') { - throw new DeveloperError('feature.geometry is required to be an object.'); - } - - if (typeof feature.properties !== 'object') { - throw new DeveloperError('feature.properties is required to be an object.'); + function processFeature(feature, notUsed, dynamicObjectCollection, crsFunction, source) { + if (typeof feature.geometry === 'undefined') { + throw new DeveloperError('feature.geometry is required.'); } if (feature.geometry === null) { @@ -218,38 +221,49 @@ define(['../Core/createGuid', dynamicObject.geoJson = feature; } else { var geometryType = feature.geometry.type; - var geometryHandler = types[geometryType]; + var geometryHandler = geometryTypes[geometryType]; if (typeof geometryHandler === 'undefined') { throw new DeveloperError('Unknown geometry type: ' + geometryType); } - geometryHandler(feature, dynamicObjectCollection, crsFunction, source); + geometryHandler(feature, feature.geometry, dynamicObjectCollection, crsFunction, source); } } - function processFeatureCollection(featureCollection, dynamicObjectCollection, crsFunction, source) { + function processFeatureCollection(featureCollection, notUsed, dynamicObjectCollection, crsFunction, source) { var features = featureCollection.features; for ( var i = 0, len = features.length; i < len; i++) { - processFeature(features[i], dynamicObjectCollection, crsFunction, source); + processFeature(features[i], undefined, dynamicObjectCollection, crsFunction, source); } } - function processGeometryCollection(geometryCollection, dynamicObjectCollection, crsFunction, source) { + function processGeometryCollection(geometryCollection, notUsed, dynamicObjectCollection, crsFunction, source) { var geometries = geometryCollection.geometries; for ( var i = 0, len = geometries.length; i < len; i++) { var geometry = geometries[i]; - types[geometry.type](geometry, dynamicObjectCollection, crsFunction, source); + var geometryType = geometry.type; + var geometryHandler = geometryTypes[geometryType]; + if (typeof geometryHandler === 'undefined') { + throw new DeveloperError('Unknown geometry type: ' + geometryType); + } + geometryHandler(geometryCollection, geometry, dynamicObjectCollection, crsFunction, source); } } - function processLineString(feature, dynamicObjectCollection, crsFunction, source) { - var id = feature.id; - if (typeof id === 'undefined') { + function createObject(geojson, dynamicObjectCollection) { + var id = geojson.id; + //We can only use the id property if it is a feature. + if (geojson.type !== 'Feature' || typeof id === 'undefined') { id = createGuid(); } var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); - dynamicObject.geoJson = feature; + dynamicObject.geoJson = geojson; + return dynamicObject; + } - var coordinates = feature.geometry.coordinates; + function processLineString(geojson, geometry, dynamicObjectCollection, crsFunction, source) { + var dynamicObject = createObject(geojson, dynamicObjectCollection); + + var coordinates = geometry.coordinates; var positions = new Array(coordinates.length); for ( var i = 0; i < coordinates.length; i++) { positions[i] = crsFunction(coordinates[i]); @@ -258,12 +272,11 @@ define(['../Core/createGuid', applyPolylineDefaults(dynamicObject); } - function processMultiLineString(feature, dynamicObjectCollection, crsFunction, source) { - var lineStrings = feature.geometry.coordinates; + function processMultiLineString(geojson, geometry, dynamicObjectCollection, crsFunction, source) { + var lineStrings = geometry.coordinates; for ( var i = 0; i < lineStrings.length; i++) { var lineString = lineStrings[i]; - var dynamicObject = dynamicObjectCollection.getOrCreateObject(createGuid()); - dynamicObject.geoJson = feature; + var dynamicObject = createObject(geojson, dynamicObjectCollection); var positions = new Array(lineString.length); for ( var z = 0; z < lineString.length; z++) { positions[z] = crsFunction(lineString[z]); @@ -273,22 +286,20 @@ define(['../Core/createGuid', } } - function processMultiPoint(feature, dynamicObjectCollection, crsFunction, source) { - var coordinates = feature.geometry.coordinates; + function processMultiPoint(geojson, geometry, dynamicObjectCollection, crsFunction, source) { + var coordinates = geometry.coordinates; for ( var i = 0; i < coordinates.length; i++) { - var dynamicObject = dynamicObjectCollection.getOrCreateObject(createGuid()); - dynamicObject.geoJson = feature; + var dynamicObject = createObject(geojson, dynamicObjectCollection); dynamicObject.position = new StaticPositionProperty(crsFunction(coordinates[i])); applyPointDefaults(dynamicObject); } } - function processMultiPolygon(feature, dynamicObjectCollection, crsFunction, source) { - var polygons = feature.geometry.coordinates; + function processMultiPolygon(geojson, geometry, dynamicObjectCollection, crsFunction, source) { + var polygons = geometry.coordinates; for ( var i = 0; i < polygons.length; i++) { var polygon = polygons[i]; - var dynamicObject = dynamicObjectCollection.getOrCreateObject(createGuid()); - dynamicObject.geoJson = feature; + var dynamicObject = createObject(geojson, dynamicObjectCollection); //TODO holes var vertexPositions = polygon[0]; @@ -303,28 +314,17 @@ define(['../Core/createGuid', } } - function processPoint(feature, dynamicObjectCollection, crsFunction, source) { - var id = feature.id; - if (typeof id === 'undefined') { - id = createGuid(); - } - var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); - dynamicObject.geoJson = feature; - - dynamicObject.position = new StaticPositionProperty(crsFunction(feature.geometry.coordinates)); + function processPoint(geojson, geometry, dynamicObjectCollection, crsFunction, source) { + var dynamicObject = createObject(geojson, dynamicObjectCollection); + dynamicObject.position = new StaticPositionProperty(crsFunction(geometry.coordinates)); applyPointDefaults(dynamicObject); } - function processPolygon(feature, dynamicObjectCollection, crsFunction, source) { - var id = feature.id; - if (typeof id === 'undefined') { - id = createGuid(); - } - var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); - dynamicObject.geoJson = feature; + function processPolygon(geojson, geometry, dynamicObjectCollection, crsFunction, source) { + var dynamicObject = createObject(geojson, dynamicObjectCollection); //TODO Holes - var coordinates = feature.geometry.coordinates[0]; + var coordinates = geometry.coordinates[0]; var positions = new Array(coordinates.length); for ( var i = 0; i < coordinates.length; i++) { positions[i] = crsFunction(coordinates[i]); @@ -333,7 +333,7 @@ define(['../Core/createGuid', applyPolygonDefaults(dynamicObject); } - var types = { + var geoJsonObjectTypes = { Feature : processFeature, FeatureCollection : processFeatureCollection, GeometryCollection : processGeometryCollection, @@ -345,5 +345,15 @@ define(['../Core/createGuid', Polygon : processPolygon }; + var geometryTypes = { + GeometryCollection : processGeometryCollection, + LineString : processLineString, + MultiLineString : processMultiLineString, + MultiPoint : processMultiPoint, + MultiPolygon : processMultiPolygon, + Point : processPoint, + Polygon : processPolygon + }; + return GeoJsonDataSource; }); \ No newline at end of file diff --git a/Source/Widgets/Viewer/viewerDragDropMixin.js b/Source/Widgets/Viewer/viewerDragDropMixin.js index 2e1610d3ad17..c0b4d3eee020 100644 --- a/Source/Widgets/Viewer/viewerDragDropMixin.js +++ b/Source/Widgets/Viewer/viewerDragDropMixin.js @@ -219,12 +219,15 @@ define([ viewer.onDropError.raiseEvent(viewer, source, error); } }; - } else if (endsWith(source, "geojson")) { + } else if (endsWith(source, 'geojson')) { return function(evt) { var geoJsonSource = new GeoJsonDataSource(); try { - geoJsonSource.load(JSON.parse(evt.target.result), source); - viewer.dataSources.add(geoJsonSource); + geoJsonSource.load(JSON.parse(evt.target.result), source).then(function() { + viewer.dataSources.add(geoJsonSource); + }, function(error) { + viewer.onDropError.raiseEvent(viewer, source, error); + }); } catch (error) { viewer.onDropError.raiseEvent(viewer, source, error); } diff --git a/Specs/Data/test.geojson b/Specs/Data/test.geojson new file mode 100644 index 000000000000..71609fe2143a --- /dev/null +++ b/Specs/Data/test.geojson @@ -0,0 +1,40 @@ +{ + "type" : "FeatureCollection", + "features" : [ + { + "type" : "Feature", + "geometry" : { + "type" : "Point", + "coordinates" : [ 102.0, 0.5 ] + }, + "properties" : { + "prop0" : "value0" + } + }, + { + "type" : "Feature", + "geometry" : { + "type" : "LineString", + "coordinates" : [ [ 102.0, 0.0 ], [ 103.0, 1.0 ], + [ 104.0, 0.0 ], [ 105.0, 1.0 ] ] + }, + "properties" : { + "prop0" : "value0", + "prop1" : 0.0 + } + }, + { + "type" : "Feature", + "geometry" : { + "type" : "Polygon", + "coordinates" : [ [ [ 100.0, 0.0 ], [ 101.0, 0.0 ], + [ 101.0, 1.0 ], [ 100.0, 1.0 ], [ 100.0, 0.0 ] ] ] + }, + "properties" : { + "prop0" : "value0", + "prop1" : { + "this" : "that" + } + } + } ] +} \ No newline at end of file diff --git a/Specs/DynamicScene/GeoJsonDataSourceSpec.js b/Specs/DynamicScene/GeoJsonDataSourceSpec.js index a4ca8ea77e45..b5a7e353d9bb 100644 --- a/Specs/DynamicScene/GeoJsonDataSourceSpec.js +++ b/Specs/DynamicScene/GeoJsonDataSourceSpec.js @@ -13,48 +13,121 @@ defineSuite(['DynamicScene/GeoJsonDataSource', "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ - var properties = { - testProperty : 1 + function coordinatesToCartesian(coordinates) { + return Ellipsoid.WGS84.cartographicToCartesian(Cartographic.fromDegrees(coordinates[0], coordinates[1])); + } + + function coordinatesArrayToCartesian(coordinates) { + var result = []; + for ( var i = 0; i < coordinates.length; i++) { + result.push(coordinatesToCartesian(coordinates[i])); + } + return result; + } + + function multiLineToCartesian(geometry) { + var coordinates = geometry.coordinates; + var result = []; + for (var i = 0; i < coordinates.length; i++) { + result.push(coordinatesArrayToCartesian(coordinates[i])); + } + return result; + } + + function polygonCoordinatesToCartesian(coordinates) { + return coordinatesArrayToCartesian(coordinates[0]); + } + + function multiPolygonCoordinatesToCartesian(coordinates) { + var result = []; + for (var i = 0; i < coordinates.length; i++) { + result.push(coordinatesArrayToCartesian(coordinates[i][0])); + } + return result; + } + + //All values are lon/lat degrees + var point = { + type : 'Point', + coordinates : [102.0, 0.5] }; - var pointCoordinates = [102.0, 0.5]; - var pointCoordinatesCartesian = Ellipsoid.WGS84.cartographicToCartesian(Cartographic.fromDegrees(pointCoordinates[0], pointCoordinates[1])); + var lineString = { + type : 'LineString', + coordinates : [[100.0, 0.0], [101.0, 1.0]] + }; - var pointFeature = { - type : "Feature", - geometry : { - type : "Point", - coordinates : pointCoordinates - }, - properties : properties + var polygon = { + type : 'Polygon', + coordinates : [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]] }; - var featureUndefinedProperties = { - type : "Feature", - geometry : { - type : "Point", - coordinates : pointCoordinates - } + var polygonWithHoles = { + type : 'Polygon', + coordinates : [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]] + }; + + var multiPoint = { + type : 'MultiPoint', + coordinates : [[100.0, 0.0], [101.0, 1.0], [101.0, 3.0]] + }; + + var multiLineString = { + type : 'MultiLineString', + coordinates : [[[100.0, 0.0], [101.0, 1.0]], [[102.0, 2.0], [103.0, 3.0]]] + }; + + var multiPolygon = { + type : 'MultiPolygon', + coordinates : [[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], + [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], + [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]] + }; + + var geometryCollection = { + type : 'GeometryCollection', + 'geometries' : [{ + type : 'Point', + coordinates : [100.0, 0.0] + }, { + type : 'LineString', + coordinates : [[101.0, 0.0], [102.0, 1.0]] + }] + }; + + var feature = { + type : 'Feature', + geometry : point + }; + + var featureWithId = { + id : 'myId', + type : 'Feature', + geometry : point }; var featureUndefinedGeometry = { - type : "Feature", - properties : properties + type : 'Feature' }; var featureNullGeometry = { - type : "Feature", - geometry : null, - properties : properties + type : 'Feature', + geometry : null + }; + + var unknownGeometry = { + type : 'TimeyWimey', + coordinates : [0, 0] }; var featureUnknownGeometry = { - type : "Feature", - geometry : { - type : "Singularity", - coordinates : pointCoordinates - }, - properties : properties + type : 'Feature', + geometry : unknownGeometry + }; + + var geometryCollectionUnknownType = { + type : 'GeometryCollection', + 'geometries' : [unknownGeometry] }; it('default constructor has expected values', function() { @@ -67,7 +140,7 @@ defineSuite(['DynamicScene/GeoJsonDataSource', expect(dataSource.getIsTimeVarying()).toEqual(false); }); - it('Works with null geomtry', function() { + it('Works with null geometry', function() { var dataSource = new GeoJsonDataSource(); dataSource.load(featureNullGeometry); @@ -82,9 +155,9 @@ defineSuite(['DynamicScene/GeoJsonDataSource', }); }); - it('Works with point geomtry', function() { + it('Works with feature', function() { var dataSource = new GeoJsonDataSource(); - dataSource.load(pointFeature); + dataSource.load(feature); var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); waitsFor(function() { @@ -92,14 +165,184 @@ defineSuite(['DynamicScene/GeoJsonDataSource', }); runs(function() { var pointObject = dynamicObjectCollection.getObjects()[0]; - expect(pointObject.geoJson).toBe(pointFeature); - expect(pointObject.position).toBeDefined(); - expect(pointObject.position.getValueCartesian()).toEqual(pointCoordinatesCartesian); + expect(pointObject.geoJson).toBe(feature); + expect(pointObject.position.getValueCartesian()).toEqual(coordinatesToCartesian(feature.geometry.coordinates)); expect(pointObject.point).toBeDefined(); }); }); - it('Fails when encountering unknown geomtry', function() { + it('Works with feature with id', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.load(featureWithId); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === 1; + }); + runs(function() { + var pointObject = dynamicObjectCollection.getObjects()[0]; + expect(pointObject.id).toEqual(featureWithId.id); + }); + }); + + it('Works with point geometry', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.load(point); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === 1; + }); + runs(function() { + var pointObject = dynamicObjectCollection.getObjects()[0]; + expect(pointObject.geoJson).toBe(point); + expect(pointObject.position.getValueCartesian()).toEqual(coordinatesToCartesian(point.coordinates)); + expect(pointObject.point).toBeDefined(); + }); + }); + + it('Works with multipoint geometry', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.load(multiPoint); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === multiPoint.coordinates.length; + }); + runs(function() { + var objects = dynamicObjectCollection.getObjects(); + var expectedPositions = coordinatesArrayToCartesian(multiPoint.coordinates); + for ( var i = 0; i < multiPoint.coordinates.length; i++) { + var object = objects[i]; + expect(object.geoJson).toBe(multiPoint); + expect(object.position.getValueCartesian()).toEqual(expectedPositions[i]); + expect(object.point).toBeDefined(); + } + }); + }); + + it('Works with lineString geometry', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.load(lineString); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === 1; + }); + runs(function() { + var object = dynamicObjectCollection.getObjects()[0]; + expect(object.geoJson).toBe(lineString); + expect(object.vertexPositions.getValueCartesian()).toEqual(coordinatesArrayToCartesian(lineString.coordinates)); + expect(object.polyline).toBeDefined(); + }); + }); + + it('Works with multiLineString geometry', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.load(multiLineString); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === 2; + }); + runs(function() { + var objects = dynamicObjectCollection.getObjects(); + var lines = multiLineToCartesian(multiLineString); + for ( var i = 0; i < multiLineString.coordinates.length; i++) { + var object = objects[i]; + expect(object.geoJson).toBe(multiLineString); + expect(object.vertexPositions.getValueCartesian()).toEqual(lines[i]); + expect(object.polyline).toBeDefined(); + } + }); + }); + + it('Works with polygon geometry', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.load(polygon); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === 1; + }); + runs(function() { + var object = dynamicObjectCollection.getObjects()[0]; + expect(object.geoJson).toBe(polygon); + expect(object.vertexPositions.getValueCartesian()).toEqual(polygonCoordinatesToCartesian(polygon.coordinates)); + expect(object.polyline).toBeDefined(); + expect(object.polygon).toBeDefined(); + }); + }); + + it('Works with polygon geometry with holes', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.load(polygonWithHoles); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === 1; + }); + runs(function() { + var object = dynamicObjectCollection.getObjects()[0]; + expect(object.geoJson).toBe(polygonWithHoles); + expect(object.vertexPositions.getValueCartesian()).toEqual(polygonCoordinatesToCartesian(polygonWithHoles.coordinates)); + expect(object.polyline).toBeDefined(); + expect(object.polygon).toBeDefined(); + }); + }); + + it('Works with multiPolygon geometry', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.load(multiPolygon); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === 2; + }); + runs(function() { + var objects = dynamicObjectCollection.getObjects(); + var positions = multiPolygonCoordinatesToCartesian(multiPolygon.coordinates); + for ( var i = 0; i < multiPolygon.coordinates.length; i++) { + var object = objects[i]; + expect(object.geoJson).toBe(multiPolygon); + expect(object.vertexPositions.getValueCartesian()).toEqual(positions[i]); + expect(object.polyline).toBeDefined(); + expect(object.polygon).toBeDefined(); + } + }); + }); + + it('Works with geometrycollection', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.load(geometryCollection); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === 2; + }); + runs(function() { + var object = dynamicObjectCollection.getObjects()[0]; + expect(object.geoJson).toBe(geometryCollection); + expect(object.position.getValueCartesian()).toEqual(coordinatesToCartesian(geometryCollection.geometries[0].coordinates)); + expect(object.point).toBeDefined(); + + object = dynamicObjectCollection.getObjects()[1]; + expect(object.geoJson).toBe(geometryCollection); + expect(object.vertexPositions.getValueCartesian()).toEqual(coordinatesArrayToCartesian(geometryCollection.geometries[1].coordinates)); + expect(object.polyline).toBeDefined(); + }); + }); + + it('loadUrl works', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.loadUrl('Data/test.geojson'); + + waitsFor(function() { + return dataSource.getDynamicObjectCollection().getObjects().length === 3; + }); + }); + + it('Fails when encountering unknown geometry', function() { var dataSource = new GeoJsonDataSource(); var failed = false; @@ -112,11 +355,11 @@ defineSuite(['DynamicScene/GeoJsonDataSource', }); }); - it('Fails with undefined properties', function() { + it('Fails with undefined geomeetry', function() { var dataSource = new GeoJsonDataSource(); var failed = false; - dataSource.load(featureUndefinedProperties).then(undefined, function(e) { + dataSource.load(featureUndefinedGeometry).then(undefined, function(e) { failed = true; }); @@ -125,11 +368,11 @@ defineSuite(['DynamicScene/GeoJsonDataSource', }); }); - it('Fails with undefined geomeetry', function() { + it('Fails with unknown geomeetry in geometryCollection', function() { var dataSource = new GeoJsonDataSource(); var failed = false; - dataSource.load(featureUndefinedGeometry).then(undefined, function(e) { + dataSource.load(geometryCollectionUnknownType).then(undefined, function(e) { failed = true; }); @@ -145,10 +388,97 @@ defineSuite(['DynamicScene/GeoJsonDataSource', }).toThrow(); }); + it('load throws with unknown geometry', function() { + var dataSource = new GeoJsonDataSource(); + expect(function() { + dataSource.load(unknownGeometry); + }).toThrow(); + }); + it('loadUrl throws with undefined Url', function() { var dataSource = new GeoJsonDataSource(); expect(function() { dataSource.loadUrl(undefined); }).toThrow(); }); + + it('load throws with null crs', function() { + var featureWithNullCrs = { + type : 'Feature', + geometry : point, + crs : null + }; + + var dataSource = new GeoJsonDataSource(); + expect(function() { + dataSource.load(featureWithNullCrs); + }).toThrow(); + }); + + it('load throws with unknown crs type', function() { + var featureWithUnknownCrsType = { + type : 'Feature', + geometry : point, + crs : { + type : 'potato' + } + }; + + var dataSource = new GeoJsonDataSource(); + expect(function() { + dataSource.load(featureWithUnknownCrsType); + }).toThrow(); + }); + + it('load throws with undefined crs properties', function() { + var featureWithUnknownCrsType = { + type : 'Feature', + geometry : point, + crs : { + type : 'name' + } + }; + + var dataSource = new GeoJsonDataSource(); + expect(function() { + dataSource.load(featureWithUnknownCrsType); + }).toThrow(); + }); + + it('load throws with unknown crs', function() { + var featureWithUnknownCrsType = { + type : 'Feature', + geometry : point, + crs : { + type : 'name', + properties : { + name : 'failMe' + } + } + }; + + var dataSource = new GeoJsonDataSource(); + expect(function() { + dataSource.load(featureWithUnknownCrsType); + }).toThrow(); + }); + + it('load throws with unknown crs link', function() { + var featureWithUnknownCrsType = { + type : 'Feature', + geometry : point, + crs : { + type : 'link', + properties : { + href : 'failMe', + type : 'failMeTwice' + } + } + }; + + var dataSource = new GeoJsonDataSource(); + expect(function() { + dataSource.load(featureWithUnknownCrsType); + }).toThrow(); + }); }); From 485985ec44afe9da9facdf6b39b0106190c425be Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Thu, 20 Jun 2013 14:37:41 -0400 Subject: [PATCH 07/20] Ongoing GeoJSON work. --- Source/DynamicScene/CzmlDataSource.js | 6 +- Source/DynamicScene/DynamicObject.js | 18 ++ Source/DynamicScene/GeoJsonDataSource.js | 271 +++++++++++++------ Source/Widgets/Viewer/viewerDragDropMixin.js | 3 +- Specs/DynamicScene/GeoJsonDataSourceSpec.js | 91 ++++++- 5 files changed, 292 insertions(+), 97 deletions(-) diff --git a/Source/DynamicScene/CzmlDataSource.js b/Source/DynamicScene/CzmlDataSource.js index 18442539ba32..a646afd773e8 100644 --- a/Source/DynamicScene/CzmlDataSource.js +++ b/Source/DynamicScene/CzmlDataSource.js @@ -65,7 +65,7 @@ define(['../Core/ClockRange', /** * Gets an event that will be raised when non-time-varying data changes * or if the return value of getIsTimeVarying changes. - * @memberof DataSource + * @memberof CzmlDataSource * * @returns {Event} The event. */ @@ -97,7 +97,7 @@ define(['../Core/ClockRange', /** * Gets the DynamicObjectCollection generated by this data source. - * @memberof DataSource + * @memberof CzmlDataSource * * @returns {DynamicObjectCollection} The collection of objects generated by this data source. */ @@ -108,7 +108,7 @@ define(['../Core/ClockRange', /** * Gets a value indicating if the data varies with simulation time. If the return value of * this function changes, the changed event will be raised. - * @memberof DataSource + * @memberof CzmlDataSource * * @returns {Boolean} True if the data is varies with simulation time, false otherwise. */ diff --git a/Source/DynamicScene/DynamicObject.js b/Source/DynamicScene/DynamicObject.js index 72dc23d3f24d..5df10c4330fc 100644 --- a/Source/DynamicScene/DynamicObject.js +++ b/Source/DynamicScene/DynamicObject.js @@ -192,6 +192,24 @@ define([ return availabilityValue; }; + /** + * Merge all of the properties of the supplied object onto this object. + * Properties which are already defined are not overwritten. + * @param other + */ + DynamicObject.prototype.merge = function(other) { + if (typeof other === 'undefined') { + throw new DeveloperError('other is required'); + } + for ( var property in other) { + if (other.hasOwnProperty(property)) { + if (this.hasOwnProperty(property) && (typeof this[property] === 'undefined')) { + this[property] = other[property]; + } + } + } + }; + /** * Processes a single CZML packet and merges its data into the provided DynamicObject's position * property. This method is not normally called directly, but is part of the array of CZML processing diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index 74d7468a2863..b3e4f04bb94f 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -7,7 +7,7 @@ define(['../Core/createGuid', '../Core/Ellipsoid', '../Core/Event', '../Core/loadJson', - '../Scene/WebMercatorProjection', + './DynamicObject', './DynamicPoint', './DynamicPolyline', './DynamicPolygon', @@ -24,7 +24,7 @@ define(['../Core/createGuid', Ellipsoid, Event, loadJson, - WebMercatorProjection, + DynamicObject, DynamicPoint, DynamicPolyline, DynamicPolygon, @@ -35,59 +35,78 @@ define(['../Core/createGuid', when) { "use strict"; - var webMercator = new WebMercatorProjection(Ellipsoid.WGS84); - - function webMercatorCrsFunction(coordinates) { - var cartographic = Cartographic.fromDegrees(coordinates[0], coordinates[1], coordinates[2]); - return webMercator.project(cartographic); - } - - function defaultCrsFunction(coordinates) { - var cartographic = Cartographic.fromDegrees(coordinates[0], coordinates[1], coordinates[2]); - return Ellipsoid.WGS84.cartographicToCartesian(cartographic); - } - + /** + * A {@link DataSource} which processes GeoJSON. + * @alias GeoJsonDataSource + * @constructor + * + * @see http://www.geojson.org/. + */ var GeoJsonDataSource = function() { this._changed = new Event(); this._error = new Event(); this._dynamicObjectCollection = new DynamicObjectCollection(); - this._crsNameHandlers = { - 'urn:ogc:def:crs:OGC:1.3:CRS84' : defaultCrsFunction, - 'EPSG:4326' : defaultCrsFunction, - 'EPSG:3857' : webMercatorCrsFunction - }; - this._crsLinkTypeHandlers = {}; - this._crsLinkHrefHandlers = {}; }; - defineProperties(GeoJsonDataSource.prototype, { - crsTransformations : { - get : function() { - return this._crsNameHandlers; - } - } - }); - + /** + * Gets an event that will be raised when non-time-varying data changes + * or if the return value of getIsTimeVarying changes. + * @memberof GeoJsonDataSource + * + * @returns {Event} The event. + */ GeoJsonDataSource.prototype.getChangedEvent = function() { return this._changed; }; + /** + * Gets an event that will be raised if an error is encountered during processing. + * @memberof GeoJsonDataSource + * + * @returns {Event} The event. + */ GeoJsonDataSource.prototype.getErrorEvent = function() { return this._error; }; + /** + * Since GeoJson is a static format, this function always returns undefined. + * @memberof GeoJsonDataSource + */ GeoJsonDataSource.prototype.getClock = function() { return undefined; }; + /** + * Gets the DynamicObjectCollection generated by this data source. + * @memberof GeoJsonDataSource + * + * @returns {DynamicObjectCollection} The collection of objects generated by this data source. + */ GeoJsonDataSource.prototype.getDynamicObjectCollection = function() { return this._dynamicObjectCollection; }; + /** + * Gets a value indicating if the data varies with simulation time. If the return value of + * this function changes, the changed event will be raised. + * @memberof GeoJsonDataSource + * + * @returns {Boolean} True if the data is varies with simulation time, false otherwise. + */ GeoJsonDataSource.prototype.getIsTimeVarying = function() { return false; }; + /** + * Asynchronously loads the GeoJSON at the provided url, replacing any existing data. + * + * @param {Object} url The url to be processed. + * + * @returns {Promise} a promise that will resolve when the GeoJSON is loaded. + * + * @exception {DeveloperError} url is required. + */ GeoJsonDataSource.prototype.loadUrl = function(url) { if (typeof url === 'undefined') { throw new DeveloperError('url is required.'); @@ -101,6 +120,22 @@ define(['../Core/createGuid', }); }; + /** + * Asynchronously loads the provided GeoJSON object, replacing any existing data. + * + * @param {Object} geoJson The object to be processed. + * @param {String} [source] The base URI of any relative links in the geoJson object. + * + * @returns {Promise} a promise that will resolve when the GeoJSON is loaded. + * + * @exception {DeveloperError} geoJson is required. + * @exception {DeveloperError} Unsupported GeoJSON object type. + * @exception {DeveloperError} crs is null. + * @exception {DeveloperError} crs.properties is undefined. + * @exception {DeveloperError} Unknown crs name. + * @exception {DeveloperError} Unable to resolve crs link. + * @exception {DeveloperError} Unknown crs type. + */ GeoJsonDataSource.prototype.load = function(geoJson, source) { if (typeof geoJson === 'undefined') { throw new DeveloperError('geoJson is required.'); @@ -119,12 +154,12 @@ define(['../Core/createGuid', throw new DeveloperError('crs is null.'); } if (typeof crs.properties === 'undefined') { - throw new DeveloperError('Invalid crs property, crs.properties is undefined.'); + throw new DeveloperError('crs.properties is undefined.'); } var properties = crs.properties; if (crs.type === 'name') { - var crsFunction = this._crsNameHandlers[properties.name]; + var crsFunction = GeoJsonDataSource.crsNames[properties.name]; if (typeof crsFunction === 'undefined') { throw new DeveloperError('Unknown crs name: ' + properties.name); } @@ -135,16 +170,16 @@ define(['../Core/createGuid', return deferred.promise; }); } else if (crs.type === 'link') { - var handler = this._crsLinkHrefHandlers[properties.href]; + var handler = GeoJsonDataSource.crsLinkHrefs[properties.href]; if (typeof handler === 'undefined') { - handler = this._crsLinkTypeHandlers[properties.type]; + handler = GeoJsonDataSource.crsLinkTypes[properties.type]; } if (typeof handler === 'undefined') { - throw new DeveloperError('Unable to resolve crs link: ' + properties); + throw new DeveloperError('Unable to resolve crs link: ' + JSON.stringify(properties)); } - crsPromise = handler(properties.href, properties.type); + crsPromise = handler(properties); } else { throw new DeveloperError('Unknown crs type: ' + crs.type); } @@ -166,25 +201,94 @@ define(['../Core/createGuid', }); }; - function applyPointDefaults(dynamicObject) { - var point = new DynamicPoint(); - point.color = new StaticProperty(Color.BLUE); - point.pixelSize = new StaticProperty(10); - point.outlineColor = new StaticProperty(Color.BLACK); - point.outlineWidth = new StaticProperty(1); - dynamicObject.point = point; + function defaultCrsFunction(coordinates) { + var cartographic = Cartographic.fromDegrees(coordinates[0], coordinates[1], coordinates[2]); + return Ellipsoid.WGS84.cartographicToCartesian(cartographic); } - function applyPolylineDefaults(dynamicObject) { - var polyline = new DynamicPolyline(); - polyline.color = new StaticProperty(Color.BLUE); - polyline.width = new StaticProperty(2); - polyline.outlineColor = new StaticProperty(Color.BLACK); - polyline.outlineWidth = new StaticProperty(1); - dynamicObject.polyline = polyline; - } + /** + * An object that maps the name of a crs to a callback function + * which takes a GeoJson coordinate and transforms it into a + * WGS84 Earth-fixed Cartesian. + * @memberof GeoJsonDataSource + * @type Object + */ + GeoJsonDataSource.crsNames = { + 'urn:ogc:def:crs:OGC:1.3:CRS84' : defaultCrsFunction, + 'EPSG:4326' : defaultCrsFunction + }; + /** + * An object that maps the href property of a crs link to a callback function + * which takes the crs properties object and returns a Promise that resolves + * to a function that takes a GeoJson coordinate and transforms it into a WGS84 Earth-fixed Cartesian. + * Items in this object take precedence over those defined in crsLinkHrefs, assuming + * the link has a type specified. + * @memberof GeoJsonDataSource + * @type Object + */ + GeoJsonDataSource.crsLinkHrefs = {}; + + /** + * An object that maps the type property of a crs link to a callback function + * which takes the crs properties object and returns a Promise that resolves + * to a function that takes a GeoJson coordinate and transforms it into a WGS84 Earth-fixed Cartesian. + * Items in crsLinkHrefs take precedence over this object. + * @memberof GeoJsonDataSource + * @type Object + */ + GeoJsonDataSource.crsLinkTypes = {}; + + /** + * The default graphics to be applied to GeoJson Point and MultiPoint geometries. + * @memberof GeoJsonDataSource + * @type DynamicObject + */ + GeoJsonDataSource.defaultPoint = new DynamicObject('GeoJsonDataSource.defaultPoint'); + + /** + * The default graphics to be applied to GeoJson LineString and MultiLineString geometries. + * @memberof GeoJsonDataSource + * @type DynamicObject + */ + GeoJsonDataSource.defaultLine = new DynamicObject('GeoJsonDataSource.defaultLine'); + + /** + * The default graphics to be applied to GeoJson Polygon and MultiPolygon geometries. + * @memberof GeoJsonDataSource + * @type DynamicObject + */ + GeoJsonDataSource.defaultPolygon = new DynamicObject('GeoJsonDataSource.defaultPolygon'); + + //Configure default point + var defaultPoint = GeoJsonDataSource.defaultPoint; + var point = new DynamicPoint(); + point.color = new StaticProperty(Color.BLUE); + point.pixelSize = new StaticProperty(10); + point.outlineColor = new StaticProperty(Color.BLACK); + point.outlineWidth = new StaticProperty(1); + defaultPoint.point = point; + + //Configure default line + var defaultLine = GeoJsonDataSource.defaultLine; + var polyline = new DynamicPolyline(); + polyline.color = new StaticProperty(Color.BLUE); + polyline.width = new StaticProperty(2); + polyline.outlineColor = new StaticProperty(Color.BLACK); + polyline.outlineWidth = new StaticProperty(1); + defaultLine.polyline = polyline; + + //Configure default polygon + var defaultPolygon = GeoJsonDataSource.defaultPolygon; var polygonMaterial = new DynamicMaterialProperty(); + polyline = new DynamicPolyline(); + polyline.color = new StaticProperty(Color.BLUE); + polyline.width = new StaticProperty(1); + polyline.outlineColor = new StaticProperty(Color.BLACK); + polyline.outlineWidth = new StaticProperty(0); + defaultPolygon.polyline = polyline; + var polygon = new DynamicPolygon(); + polygon.material = polygonMaterial; polygonMaterial.processCzmlIntervals({ solidColor : { color : { @@ -192,20 +296,31 @@ define(['../Core/createGuid', } } }, undefined, undefined); + defaultPolygon.polygon = polygon; - function applyPolygonDefaults(dynamicObject) { - var polyline = new DynamicPolyline(); - polyline.color = new StaticProperty(Color.BLUE); - polyline.width = new StaticProperty(1); - polyline.outlineColor = new StaticProperty(Color.BLACK); - polyline.outlineWidth = new StaticProperty(0); - dynamicObject.polyline = polyline; - - var polygon = new DynamicPolygon(); - polygon.material = polygonMaterial; - dynamicObject.polygon = polygon; + + //GeoJson specifies only the Feature object has a usable id property + //But since "multi" geometries create multiple dynamicObject, + //we can't use it for them either. + function createObject(geojson, dynamicObjectCollection) { + var id = geojson.id; + if (typeof id === 'undefined' || geojson.type !== 'Feature') { + id = createGuid(); + } else { + var i = 2; + var finalId = id; + while (typeof dynamicObjectCollection.getObject(finalId) !== 'undefined') { + finalId = id + "_" + i; + i++; + } + id = finalId; + } + var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); + dynamicObject.geoJson = geojson; + return dynamicObject; } + // GeoJson processing functions function processFeature(feature, notUsed, dynamicObjectCollection, crsFunction, source) { if (typeof feature.geometry === 'undefined') { throw new DeveloperError('feature.geometry is required.'); @@ -213,12 +328,7 @@ define(['../Core/createGuid', if (feature.geometry === null) { //Null geometry is allowed, so just create an empty dynamicObject instance for it. - var id = feature.id; - if (typeof id === 'undefined') { - id = createGuid(); - } - var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); - dynamicObject.geoJson = feature; + createObject(feature, dynamicObjectCollection); } else { var geometryType = feature.geometry.type; var geometryHandler = geometryTypes[geometryType]; @@ -236,7 +346,7 @@ define(['../Core/createGuid', } } - function processGeometryCollection(geometryCollection, notUsed, dynamicObjectCollection, crsFunction, source) { + function processGeometryCollection(geoJson, geometryCollection, dynamicObjectCollection, crsFunction, source) { var geometries = geometryCollection.geometries; for ( var i = 0, len = geometries.length; i < len; i++) { var geometry = geometries[i]; @@ -245,21 +355,10 @@ define(['../Core/createGuid', if (typeof geometryHandler === 'undefined') { throw new DeveloperError('Unknown geometry type: ' + geometryType); } - geometryHandler(geometryCollection, geometry, dynamicObjectCollection, crsFunction, source); + geometryHandler(geoJson, geometry, dynamicObjectCollection, crsFunction, source); } } - function createObject(geojson, dynamicObjectCollection) { - var id = geojson.id; - //We can only use the id property if it is a feature. - if (geojson.type !== 'Feature' || typeof id === 'undefined') { - id = createGuid(); - } - var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); - dynamicObject.geoJson = geojson; - return dynamicObject; - } - function processLineString(geojson, geometry, dynamicObjectCollection, crsFunction, source) { var dynamicObject = createObject(geojson, dynamicObjectCollection); @@ -268,8 +367,8 @@ define(['../Core/createGuid', for ( var i = 0; i < coordinates.length; i++) { positions[i] = crsFunction(coordinates[i]); } + dynamicObject.merge(GeoJsonDataSource.defaultLine); dynamicObject.vertexPositions = new StaticPositionProperty(positions); - applyPolylineDefaults(dynamicObject); } function processMultiLineString(geojson, geometry, dynamicObjectCollection, crsFunction, source) { @@ -281,8 +380,8 @@ define(['../Core/createGuid', for ( var z = 0; z < lineString.length; z++) { positions[z] = crsFunction(lineString[z]); } + dynamicObject.merge(GeoJsonDataSource.defaultLine); dynamicObject.vertexPositions = new StaticPositionProperty(positions); - applyPolylineDefaults(dynamicObject); } } @@ -290,8 +389,8 @@ define(['../Core/createGuid', var coordinates = geometry.coordinates; for ( var i = 0; i < coordinates.length; i++) { var dynamicObject = createObject(geojson, dynamicObjectCollection); + dynamicObject.merge(GeoJsonDataSource.defaultPoint); dynamicObject.position = new StaticPositionProperty(crsFunction(coordinates[i])); - applyPointDefaults(dynamicObject); } } @@ -308,16 +407,16 @@ define(['../Core/createGuid', for ( var z = 0; z < vertexPositions.length; z++) { positions[z] = crsFunction(vertexPositions[z]); } + dynamicObject.merge(GeoJsonDataSource.defaultPolygon); dynamicObject.vertexPositions = new StaticPositionProperty(positions); - applyPolygonDefaults(dynamicObject); } } } function processPoint(geojson, geometry, dynamicObjectCollection, crsFunction, source) { var dynamicObject = createObject(geojson, dynamicObjectCollection); + dynamicObject.merge(GeoJsonDataSource.defaultPoint); dynamicObject.position = new StaticPositionProperty(crsFunction(geometry.coordinates)); - applyPointDefaults(dynamicObject); } function processPolygon(geojson, geometry, dynamicObjectCollection, crsFunction, source) { @@ -329,8 +428,8 @@ define(['../Core/createGuid', for ( var i = 0; i < coordinates.length; i++) { positions[i] = crsFunction(coordinates[i]); } + dynamicObject.merge(GeoJsonDataSource.defaultPolygon); dynamicObject.vertexPositions = new StaticPositionProperty(positions); - applyPolygonDefaults(dynamicObject); } var geoJsonObjectTypes = { diff --git a/Source/Widgets/Viewer/viewerDragDropMixin.js b/Source/Widgets/Viewer/viewerDragDropMixin.js index c0b4d3eee020..c8b2eefdd1ff 100644 --- a/Source/Widgets/Viewer/viewerDragDropMixin.js +++ b/Source/Widgets/Viewer/viewerDragDropMixin.js @@ -233,8 +233,7 @@ define([ } }; } else { - console.log('Unrecognized file extension: ' + source); - window.alert('Unrecognized file extension: ' + source); + viewer.onDropError.raiseEvent(viewer, source, 'Unrecognized file extension: ' + source); } } diff --git a/Specs/DynamicScene/GeoJsonDataSourceSpec.js b/Specs/DynamicScene/GeoJsonDataSourceSpec.js index b5a7e353d9bb..0a72438a9cb3 100644 --- a/Specs/DynamicScene/GeoJsonDataSourceSpec.js +++ b/Specs/DynamicScene/GeoJsonDataSourceSpec.js @@ -2,14 +2,18 @@ defineSuite(['DynamicScene/GeoJsonDataSource', 'DynamicScene/DynamicObjectCollection', 'Core/Cartographic', + 'Core/Cartesian3', 'Core/Ellipsoid', - 'Core/Event' + 'Core/Event', + 'ThirdParty/when' ], function( GeoJsonDataSource, DynamicObjectCollection, Cartographic, + Cartesian3, Ellipsoid, - Event) { + Event, + when) { "use strict"; /*global jasmine,describe,xdescribe,it,xit,expect,beforeEach,afterEach,beforeAll,afterAll,spyOn,runs,waits,waitsFor*/ @@ -46,12 +50,33 @@ defineSuite(['DynamicScene/GeoJsonDataSource', return result; } - //All values are lon/lat degrees var point = { type : 'Point', coordinates : [102.0, 0.5] }; + var pointNamedCrs = { + type : 'Point', + coordinates : [102.0, 0.5], + crs : { + type : 'name', + properties : { + name : 'EPSG:4326' + } + } + }; + + var pointCrsLinkHref = { + type : 'Point', + coordinates : [102.0, 0.5], + crs : { + type : 'link', + properties : { + href : 'http://crs.invalid' + } + } + }; + var lineString = { type : 'LineString', coordinates : [[100.0, 0.0], [101.0, 1.0]] @@ -103,7 +128,7 @@ defineSuite(['DynamicScene/GeoJsonDataSource', var featureWithId = { id : 'myId', type : 'Feature', - geometry : point + geometry : geometryCollection }; var featureUndefinedGeometry = { @@ -177,11 +202,13 @@ defineSuite(['DynamicScene/GeoJsonDataSource', var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); waitsFor(function() { - return dynamicObjectCollection.getObjects().length === 1; + return dynamicObjectCollection.getObjects().length === 2; }); runs(function() { var pointObject = dynamicObjectCollection.getObjects()[0]; expect(pointObject.id).toEqual(featureWithId.id); + var lineString = dynamicObjectCollection.getObjects()[1]; + expect(lineString.id).toEqual(featureWithId.id + '_2'); }); }); @@ -333,6 +360,45 @@ defineSuite(['DynamicScene/GeoJsonDataSource', }); }); + it('Works with named crs', function() { + var dataSource = new GeoJsonDataSource(); + dataSource.load(pointNamedCrs); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === 1; + }); + runs(function() { + var pointObject = dynamicObjectCollection.getObjects()[0]; + expect(pointObject.position.getValueCartesian()).toEqual(coordinatesToCartesian(point.coordinates)); + }); + }); + + it('Works with link crs href', function() { + var projectedPosition = new Cartesian3(1, 2, 3); + + var dataSource = new GeoJsonDataSource(); + GeoJsonDataSource.crsLinkHrefs[pointCrsLinkHref.crs.properties.href] = function(properties) { + expect(properties).toBe(pointCrsLinkHref.crs.properties); + return when(properties.href, function(href) { + return function(coordinate) { + expect(coordinate).toBe(pointCrsLinkHref.coordinates); + return projectedPosition; + }; + }); + }; + dataSource.load(pointCrsLinkHref); + + var dynamicObjectCollection = dataSource.getDynamicObjectCollection(); + waitsFor(function() { + return dynamicObjectCollection.getObjects().length === 1; + }); + runs(function() { + var pointObject = dynamicObjectCollection.getObjects()[0]; + expect(pointObject.position.getValueCartesian()).toEqual(projectedPosition); + }); + }); + it('loadUrl works', function() { var dataSource = new GeoJsonDataSource(); dataSource.loadUrl('Data/test.geojson'); @@ -402,6 +468,18 @@ defineSuite(['DynamicScene/GeoJsonDataSource', }).toThrow(); }); + it('loadUrl raises error with invalud url', function() { + var dataSource = new GeoJsonDataSource(); + var thrown = false; + dataSource.getErrorEvent().addEventListener(function() { + thrown = true; + }); + dataSource.loadUrl('invalid.geojson'); + waitsFor(function() { + return thrown; + }); + }); + it('load throws with null crs', function() { var featureWithNullCrs = { type : 'Feature', @@ -420,7 +498,8 @@ defineSuite(['DynamicScene/GeoJsonDataSource', type : 'Feature', geometry : point, crs : { - type : 'potato' + type : 'potato', + properties : {} } }; From 83fdd2fc61887b8745face7bc808f2655c879102 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Thu, 20 Jun 2013 16:05:07 -0400 Subject: [PATCH 08/20] Fix failing tests. --- Specs/Widgets/Viewer/viewerDragDropMixinSpec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Specs/Widgets/Viewer/viewerDragDropMixinSpec.js b/Specs/Widgets/Viewer/viewerDragDropMixinSpec.js index 73b409158753..1ef8a40ebf69 100644 --- a/Specs/Widgets/Viewer/viewerDragDropMixinSpec.js +++ b/Specs/Widgets/Viewer/viewerDragDropMixinSpec.js @@ -93,7 +93,7 @@ defineSuite([ var mockEvent = { dataTransfer : { files : [{ - name : 'czml1', + name : 'czml1.czml', czmlString : JSON.stringify(czml1) }] }, @@ -129,10 +129,10 @@ defineSuite([ var mockEvent = { dataTransfer : { files : [{ - name : 'czml1', + name : 'czml1.czml', czmlString : JSON.stringify(czml1) }, { - name : 'czml2', + name : 'czml2.czml', czmlString : JSON.stringify(czml2) }] }, @@ -171,10 +171,10 @@ defineSuite([ var mockEvent = { dataTransfer : { files : [{ - name : 'czml1', + name : 'czml1.czml', czmlString : JSON.stringify(czml1) }, { - name : 'czml2', + name : 'czml2.czml', czmlString : JSON.stringify(czml2) }] }, @@ -254,7 +254,7 @@ defineSuite([ var mockEvent = { dataTransfer : { files : [{ - name : 'czml1', + name : 'czml1.czml', czmlString : 'bad JSON' }] }, @@ -270,7 +270,7 @@ defineSuite([ var called = false; var callback = function(viewerArg, source, error) { expect(viewerArg).toBe(viewer); - expect(source).toEqual('czml1'); + expect(source).toEqual('czml1.czml'); expect(error).toBeInstanceOf(SyntaxError); called = true; }; @@ -291,7 +291,7 @@ defineSuite([ var mockEvent = { dataTransfer : { files : [{ - name : 'czml1', + name : 'czml1.czml', errorMessage : 'bad JSON' }] }, From b611ffd9e52f2dd32c3898ca6d737a611f5f6f41 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Thu, 20 Jun 2013 16:14:57 -0400 Subject: [PATCH 09/20] Temporarily remove StaticProperty/StaticPosition property. Have GeoJsonDataSource use private versions of them instead until I do them "for real". --- Source/DynamicScene/DynamicObject.js | 3 +- Source/DynamicScene/GeoJsonDataSource.js | 72 +++++++++++++------ Source/DynamicScene/StaticPositionProperty.js | 25 ------- Source/DynamicScene/StaticProperty.js | 41 ----------- 4 files changed, 52 insertions(+), 89 deletions(-) delete mode 100644 Source/DynamicScene/StaticPositionProperty.js delete mode 100644 Source/DynamicScene/StaticProperty.js diff --git a/Source/DynamicScene/DynamicObject.js b/Source/DynamicScene/DynamicObject.js index 5df10c4330fc..267594e2f136 100644 --- a/Source/DynamicScene/DynamicObject.js +++ b/Source/DynamicScene/DynamicObject.js @@ -195,7 +195,8 @@ define([ /** * Merge all of the properties of the supplied object onto this object. * Properties which are already defined are not overwritten. - * @param other + * @param other {DynamicObject} The object to merge. + * @private */ DynamicObject.prototype.merge = function(other) { if (typeof other === 'undefined') { diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index b3e4f04bb94f..4ec7f7980a2f 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -13,8 +13,6 @@ define(['../Core/createGuid', './DynamicPolygon', './DynamicMaterialProperty', './DynamicObjectCollection', - './StaticProperty', - './StaticPositionProperty', '../ThirdParty/when'], function( createGuid, Cartographic, @@ -30,11 +28,41 @@ define(['../Core/createGuid', DynamicPolygon, DynamicMaterialProperty, DynamicObjectCollection, - StaticProperty, - StaticPositionProperty, when) { "use strict"; + var ConstantProperty = function(value) { + this._value = value; + }; + + ConstantProperty.prototype.getValue = function(time, result) { + var value = this._value; + if (typeof value.clone === 'function') { + return value.clone(result); + } + return value; + }; + + ConstantProperty.prototype.setValue = function(value) { + this._value = value; + }; + + var ConstantPositionProperty = function(value) { + this._value = value; + }; + + ConstantPositionProperty.prototype.getValueCartesian = function(time, result) { + var value = this._value; + if (typeof value.clone === 'function') { + return value.clone(result); + } + return value; + }; + + ConstantPositionProperty.prototype.setValue = function(value) { + this._value = value; + }; + /** * A {@link DataSource} which processes GeoJSON. * @alias GeoJsonDataSource @@ -263,29 +291,29 @@ define(['../Core/createGuid', //Configure default point var defaultPoint = GeoJsonDataSource.defaultPoint; var point = new DynamicPoint(); - point.color = new StaticProperty(Color.BLUE); - point.pixelSize = new StaticProperty(10); - point.outlineColor = new StaticProperty(Color.BLACK); - point.outlineWidth = new StaticProperty(1); + point.color = new ConstantProperty(Color.BLUE); + point.pixelSize = new ConstantProperty(10); + point.outlineColor = new ConstantProperty(Color.BLACK); + point.outlineWidth = new ConstantProperty(1); defaultPoint.point = point; //Configure default line var defaultLine = GeoJsonDataSource.defaultLine; var polyline = new DynamicPolyline(); - polyline.color = new StaticProperty(Color.BLUE); - polyline.width = new StaticProperty(2); - polyline.outlineColor = new StaticProperty(Color.BLACK); - polyline.outlineWidth = new StaticProperty(1); + polyline.color = new ConstantProperty(Color.BLUE); + polyline.width = new ConstantProperty(2); + polyline.outlineColor = new ConstantProperty(Color.BLACK); + polyline.outlineWidth = new ConstantProperty(1); defaultLine.polyline = polyline; //Configure default polygon var defaultPolygon = GeoJsonDataSource.defaultPolygon; var polygonMaterial = new DynamicMaterialProperty(); polyline = new DynamicPolyline(); - polyline.color = new StaticProperty(Color.BLUE); - polyline.width = new StaticProperty(1); - polyline.outlineColor = new StaticProperty(Color.BLACK); - polyline.outlineWidth = new StaticProperty(0); + polyline.color = new ConstantProperty(Color.BLUE); + polyline.width = new ConstantProperty(1); + polyline.outlineColor = new ConstantProperty(Color.BLACK); + polyline.outlineWidth = new ConstantProperty(0); defaultPolygon.polyline = polyline; var polygon = new DynamicPolygon(); polygon.material = polygonMaterial; @@ -368,7 +396,7 @@ define(['../Core/createGuid', positions[i] = crsFunction(coordinates[i]); } dynamicObject.merge(GeoJsonDataSource.defaultLine); - dynamicObject.vertexPositions = new StaticPositionProperty(positions); + dynamicObject.vertexPositions = new ConstantPositionProperty(positions); } function processMultiLineString(geojson, geometry, dynamicObjectCollection, crsFunction, source) { @@ -381,7 +409,7 @@ define(['../Core/createGuid', positions[z] = crsFunction(lineString[z]); } dynamicObject.merge(GeoJsonDataSource.defaultLine); - dynamicObject.vertexPositions = new StaticPositionProperty(positions); + dynamicObject.vertexPositions = new ConstantPositionProperty(positions); } } @@ -390,7 +418,7 @@ define(['../Core/createGuid', for ( var i = 0; i < coordinates.length; i++) { var dynamicObject = createObject(geojson, dynamicObjectCollection); dynamicObject.merge(GeoJsonDataSource.defaultPoint); - dynamicObject.position = new StaticPositionProperty(crsFunction(coordinates[i])); + dynamicObject.position = new ConstantPositionProperty(crsFunction(coordinates[i])); } } @@ -408,7 +436,7 @@ define(['../Core/createGuid', positions[z] = crsFunction(vertexPositions[z]); } dynamicObject.merge(GeoJsonDataSource.defaultPolygon); - dynamicObject.vertexPositions = new StaticPositionProperty(positions); + dynamicObject.vertexPositions = new ConstantPositionProperty(positions); } } } @@ -416,7 +444,7 @@ define(['../Core/createGuid', function processPoint(geojson, geometry, dynamicObjectCollection, crsFunction, source) { var dynamicObject = createObject(geojson, dynamicObjectCollection); dynamicObject.merge(GeoJsonDataSource.defaultPoint); - dynamicObject.position = new StaticPositionProperty(crsFunction(geometry.coordinates)); + dynamicObject.position = new ConstantPositionProperty(crsFunction(geometry.coordinates)); } function processPolygon(geojson, geometry, dynamicObjectCollection, crsFunction, source) { @@ -429,7 +457,7 @@ define(['../Core/createGuid', positions[i] = crsFunction(coordinates[i]); } dynamicObject.merge(GeoJsonDataSource.defaultPolygon); - dynamicObject.vertexPositions = new StaticPositionProperty(positions); + dynamicObject.vertexPositions = new ConstantPositionProperty(positions); } var geoJsonObjectTypes = { diff --git a/Source/DynamicScene/StaticPositionProperty.js b/Source/DynamicScene/StaticPositionProperty.js deleted file mode 100644 index 73279e171559..000000000000 --- a/Source/DynamicScene/StaticPositionProperty.js +++ /dev/null @@ -1,25 +0,0 @@ -/*global define*/ -define([ - '../Core/DeveloperError' - ], function( - DeveloperError) { - "use strict"; - - var StaticPositionProperty = function(value) { - this._value = value; - }; - - StaticPositionProperty.prototype.getValueCartesian = function(time, result) { - var value = this._value; - if (typeof value.clone === 'function') { - return value.clone(result); - } - return value; - }; - - StaticPositionProperty.prototype.setValue = function(value) { - this._value = value; - }; - - return StaticPositionProperty; -}); \ No newline at end of file diff --git a/Source/DynamicScene/StaticProperty.js b/Source/DynamicScene/StaticProperty.js deleted file mode 100644 index aa425a4c53ea..000000000000 --- a/Source/DynamicScene/StaticProperty.js +++ /dev/null @@ -1,41 +0,0 @@ -/*global define*/ -define([ - '../Core/DeveloperError', - '../Core/Iso8601', - '../Core/JulianDate', - '../Core/TimeInterval', - '../Core/TimeIntervalCollection', - '../Core/binarySearch', - '../Core/HermitePolynomialApproximation', - '../Core/LinearApproximation', - '../Core/LagrangePolynomialApproximation' - ], function( - DeveloperError, - Iso8601, - JulianDate, - TimeInterval, - TimeIntervalCollection, - binarySearch, - HermitePolynomialApproximation, - LinearApproximation, - LagrangePolynomialApproximation) { - "use strict"; - - var StaticProperty = function(value) { - this._value = value; - }; - - StaticProperty.prototype.getValue = function(time, result) { - var value = this._value; - if (typeof value.clone === 'function') { - return value.clone(result); - } - return value; - }; - - StaticProperty.prototype.setValue = function(value) { - this._value = value; - }; - - return StaticProperty; -}); \ No newline at end of file From ae572bef427ec0d8bed22495ab4352fb9208fbd4 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Thu, 20 Jun 2013 17:27:21 -0400 Subject: [PATCH 10/20] More cleanup before pull request. --- Apps/CesiumViewer/CesiumViewer.js | 13 +- Source/DynamicScene/ConstantProperty.js | 34 +++ Source/DynamicScene/GeoJsonDataSource.js | 232 +++++++++---------- Source/Widgets/Viewer/viewerDragDropMixin.js | 4 +- 4 files changed, 161 insertions(+), 122 deletions(-) create mode 100644 Source/DynamicScene/ConstantProperty.js diff --git a/Apps/CesiumViewer/CesiumViewer.js b/Apps/CesiumViewer/CesiumViewer.js index 9668076590c7..35916dfa9bf9 100644 --- a/Apps/CesiumViewer/CesiumViewer.js +++ b/Apps/CesiumViewer/CesiumViewer.js @@ -1,6 +1,7 @@ /*global define*/ define([ 'DynamicScene/CzmlDataSource', + 'DynamicScene/GeoJsonDataSource', 'Scene/PerformanceDisplay', 'Widgets/checkForChromeFrame', 'Widgets/Viewer/Viewer', @@ -9,6 +10,7 @@ define([ 'domReady!' ], function( CzmlDataSource, + GeoJsonDataSource, PerformanceDisplay, checkForChromeFrame, Viewer, @@ -50,6 +52,10 @@ define([ window.alert(e); }); + function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; + } + function startup() { var viewer = new Viewer('cesiumContainer'); viewer.extend(viewerDragDropMixin); @@ -75,7 +81,12 @@ define([ } if (typeof endUserOptions.source !== 'undefined') { - var source = new CzmlDataSource(); + var source; + if (endsWith(endUserOptions.source.toUpperCase(), "GEOJSON")) { + source = new GeoJsonDataSource(); + } else { + source = new CzmlDataSource(); + } source.loadUrl(endUserOptions.source).then(function() { viewer.dataSources.add(source); diff --git a/Source/DynamicScene/ConstantProperty.js b/Source/DynamicScene/ConstantProperty.js new file mode 100644 index 000000000000..cc5c49d3316a --- /dev/null +++ b/Source/DynamicScene/ConstantProperty.js @@ -0,0 +1,34 @@ +/*global define*/ +define(function() { + "use strict"; + + /** + * Represents a single value which does not change with regard to simulation time. + * + * @alias ConstantProperty + * @constructor + * + * @see DynamicProperty + */ + var ConstantProperty = function(value) { + this._value = value; + }; + + /** + * Returns the value of the property. + * @memberof ConstantProperty + * + * @param {JulianDate} time The time for which to retrieve the value. This value is not used. + * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned. + * @returns The modified result parameter or a new instance if the result parameter was not supplied. + */ + ConstantProperty.prototype.getValue = function(time, result) { + var value = this._value; + if (typeof value !== 'undefined' && typeof value.clone === 'function') { + return value.clone(result); + } + return value; + }; + + return ConstantProperty; +}); \ No newline at end of file diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index 4ec7f7980a2f..fe8346ce2310 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -7,6 +7,7 @@ define(['../Core/createGuid', '../Core/Ellipsoid', '../Core/Event', '../Core/loadJson', + './ConstantProperty', './DynamicObject', './DynamicPoint', './DynamicPolyline', @@ -22,6 +23,7 @@ define(['../Core/createGuid', Ellipsoid, Event, loadJson, + ConstantProperty, DynamicObject, DynamicPoint, DynamicPolyline, @@ -29,24 +31,11 @@ define(['../Core/createGuid', DynamicMaterialProperty, DynamicObjectCollection, when) { - "use strict"; - - var ConstantProperty = function(value) { - this._value = value; - }; - - ConstantProperty.prototype.getValue = function(time, result) { - var value = this._value; - if (typeof value.clone === 'function') { - return value.clone(result); - } - return value; - }; - - ConstantProperty.prototype.setValue = function(value) { - this._value = value; - }; + "use strict"; + //DynamicPosiionProperty is pretty hard to use with non-CZML based data + //For now we create two of our own properties for exposing GeoJSON + //data. var ConstantPositionProperty = function(value) { this._value = value; }; @@ -64,16 +53,81 @@ define(['../Core/createGuid', }; /** - * A {@link DataSource} which processes GeoJSON. + * A {@link DataSource} which processes GeoJSON. Since GeoJSON has no standard for styling content, + * we provide default graphics via the defaultPoint, defaultLine, and defaultPolygon properties. + * Any changes to these objects will affect the resulting {@link DynamicObject} collection. * @alias GeoJsonDataSource * @constructor * * @see http://www.geojson.org/. + * + * @example + * var dataSource = new GeoJsonDataSource(); + * dataSource.loadUrl('http://example.invalid/sample.geojson'); */ var GeoJsonDataSource = function() { + //default point + var defaultPoint = new DynamicObject('GeoJsonDataSource.defaultPoint'); + var point = new DynamicPoint(); + point.color = new ConstantProperty(Color.BLUE); + point.pixelSize = new ConstantProperty(10); + point.outlineColor = new ConstantProperty(Color.BLACK); + point.outlineWidth = new ConstantProperty(1); + defaultPoint.point = point; + + //default line + var defaultLine = new DynamicObject('GeoJsonDataSource.defaultLine'); + var polyline = new DynamicPolyline(); + polyline.color = new ConstantProperty(Color.BLUE); + polyline.width = new ConstantProperty(2); + polyline.outlineColor = new ConstantProperty(Color.BLACK); + polyline.outlineWidth = new ConstantProperty(1); + defaultLine.polyline = polyline; + + //default polygon + var defaultPolygon = new DynamicObject('GeoJsonDataSource.defaultPolygon'); + var polygonMaterial = new DynamicMaterialProperty(); + polyline = new DynamicPolyline(); + polyline.color = new ConstantProperty(Color.BLUE); + polyline.width = new ConstantProperty(1); + polyline.outlineColor = new ConstantProperty(Color.BLACK); + polyline.outlineWidth = new ConstantProperty(0); + defaultPolygon.polyline = polyline; + var polygon = new DynamicPolygon(); + polygon.material = polygonMaterial; + polygonMaterial.processCzmlIntervals({ + solidColor : { + color : { + rgba : [0, 0, 255, 25.5] + } + } + }, undefined, undefined); + defaultPolygon.polygon = polygon; + this._changed = new Event(); this._error = new Event(); this._dynamicObjectCollection = new DynamicObjectCollection(); + + /** + * Gets or sets the default graphics to be applied to GeoJson Point and MultiPoint geometries. + * @memberof GeoJsonDataSource + * @type DynamicObject + */ + this.defaultPoint = defaultPoint; + + /** + * Gets or sets the default graphics to be applied to GeoJson LineString and MultiLineString geometries. + * @memberof GeoJsonDataSource + * @type DynamicObject + */ + this.defaultLine = defaultLine; + + /** + * Gets or sets the default graphics to be applied to GeoJson Polygon and MultiPolygon geometries. + * @memberof GeoJsonDataSource + * @type DynamicObject + */ + this.defaultPolygon = defaultPolygon; }; /** @@ -224,7 +278,7 @@ define(['../Core/createGuid', var that = this; return crsPromise.then(function(crsFunction) { - typeHandler(geoJson, geoJson, that._dynamicObjectCollection, crsFunction, source); + typeHandler(that, geoJson, geoJson, crsFunction, source); that._changed.raiseEvent(that); }); }; @@ -267,66 +321,6 @@ define(['../Core/createGuid', */ GeoJsonDataSource.crsLinkTypes = {}; - /** - * The default graphics to be applied to GeoJson Point and MultiPoint geometries. - * @memberof GeoJsonDataSource - * @type DynamicObject - */ - GeoJsonDataSource.defaultPoint = new DynamicObject('GeoJsonDataSource.defaultPoint'); - - /** - * The default graphics to be applied to GeoJson LineString and MultiLineString geometries. - * @memberof GeoJsonDataSource - * @type DynamicObject - */ - GeoJsonDataSource.defaultLine = new DynamicObject('GeoJsonDataSource.defaultLine'); - - /** - * The default graphics to be applied to GeoJson Polygon and MultiPolygon geometries. - * @memberof GeoJsonDataSource - * @type DynamicObject - */ - GeoJsonDataSource.defaultPolygon = new DynamicObject('GeoJsonDataSource.defaultPolygon'); - - //Configure default point - var defaultPoint = GeoJsonDataSource.defaultPoint; - var point = new DynamicPoint(); - point.color = new ConstantProperty(Color.BLUE); - point.pixelSize = new ConstantProperty(10); - point.outlineColor = new ConstantProperty(Color.BLACK); - point.outlineWidth = new ConstantProperty(1); - defaultPoint.point = point; - - //Configure default line - var defaultLine = GeoJsonDataSource.defaultLine; - var polyline = new DynamicPolyline(); - polyline.color = new ConstantProperty(Color.BLUE); - polyline.width = new ConstantProperty(2); - polyline.outlineColor = new ConstantProperty(Color.BLACK); - polyline.outlineWidth = new ConstantProperty(1); - defaultLine.polyline = polyline; - - //Configure default polygon - var defaultPolygon = GeoJsonDataSource.defaultPolygon; - var polygonMaterial = new DynamicMaterialProperty(); - polyline = new DynamicPolyline(); - polyline.color = new ConstantProperty(Color.BLUE); - polyline.width = new ConstantProperty(1); - polyline.outlineColor = new ConstantProperty(Color.BLACK); - polyline.outlineWidth = new ConstantProperty(0); - defaultPolygon.polyline = polyline; - var polygon = new DynamicPolygon(); - polygon.material = polygonMaterial; - polygonMaterial.processCzmlIntervals({ - solidColor : { - color : { - rgba : [0, 0, 255, 25.5] - } - } - }, undefined, undefined); - defaultPolygon.polygon = polygon; - - //GeoJson specifies only the Feature object has a usable id property //But since "multi" geometries create multiple dynamicObject, //we can't use it for them either. @@ -349,32 +343,32 @@ define(['../Core/createGuid', } // GeoJson processing functions - function processFeature(feature, notUsed, dynamicObjectCollection, crsFunction, source) { + function processFeature(dataSource, feature, notUsed, crsFunction, source) { if (typeof feature.geometry === 'undefined') { throw new DeveloperError('feature.geometry is required.'); } if (feature.geometry === null) { //Null geometry is allowed, so just create an empty dynamicObject instance for it. - createObject(feature, dynamicObjectCollection); + createObject(feature, dataSource._dynamicObjectCollection); } else { var geometryType = feature.geometry.type; var geometryHandler = geometryTypes[geometryType]; if (typeof geometryHandler === 'undefined') { throw new DeveloperError('Unknown geometry type: ' + geometryType); } - geometryHandler(feature, feature.geometry, dynamicObjectCollection, crsFunction, source); + geometryHandler(dataSource, feature, feature.geometry, crsFunction, source); } } - function processFeatureCollection(featureCollection, notUsed, dynamicObjectCollection, crsFunction, source) { + function processFeatureCollection(dataSource, featureCollection, notUsed, crsFunction, source) { var features = featureCollection.features; for ( var i = 0, len = features.length; i < len; i++) { - processFeature(features[i], undefined, dynamicObjectCollection, crsFunction, source); + processFeature(dataSource, features[i], undefined, crsFunction, source); } } - function processGeometryCollection(geoJson, geometryCollection, dynamicObjectCollection, crsFunction, source) { + function processGeometryCollection(dataSource, geoJson, geometryCollection, crsFunction, source) { var geometries = geometryCollection.geometries; for ( var i = 0, len = geometries.length; i < len; i++) { var geometry = geometries[i]; @@ -383,50 +377,69 @@ define(['../Core/createGuid', if (typeof geometryHandler === 'undefined') { throw new DeveloperError('Unknown geometry type: ' + geometryType); } - geometryHandler(geoJson, geometry, dynamicObjectCollection, crsFunction, source); + geometryHandler(dataSource, geoJson, geometry, crsFunction, source); + } + } + + function processPoint(dataSource, geojson, geometry, crsFunction, source) { + var dynamicObject = createObject(geojson, dataSource._dynamicObjectCollection); + dynamicObject.merge(dataSource.defaultPoint); + dynamicObject.position = new ConstantPositionProperty(crsFunction(geometry.coordinates)); + } + + function processMultiPoint(dataSource, geojson, geometry, crsFunction, source) { + var coordinates = geometry.coordinates; + for ( var i = 0; i < coordinates.length; i++) { + var dynamicObject = createObject(geojson, dataSource._dynamicObjectCollection); + dynamicObject.merge(dataSource.defaultPoint); + dynamicObject.position = new ConstantPositionProperty(crsFunction(coordinates[i])); } } - function processLineString(geojson, geometry, dynamicObjectCollection, crsFunction, source) { - var dynamicObject = createObject(geojson, dynamicObjectCollection); + function processLineString(dataSource, geojson, geometry, crsFunction, source) { + var dynamicObject = createObject(geojson, dataSource._dynamicObjectCollection); var coordinates = geometry.coordinates; var positions = new Array(coordinates.length); for ( var i = 0; i < coordinates.length; i++) { positions[i] = crsFunction(coordinates[i]); } - dynamicObject.merge(GeoJsonDataSource.defaultLine); + dynamicObject.merge(dataSource.defaultLine); dynamicObject.vertexPositions = new ConstantPositionProperty(positions); } - function processMultiLineString(geojson, geometry, dynamicObjectCollection, crsFunction, source) { + function processMultiLineString(dataSource, geojson, geometry, crsFunction, source) { var lineStrings = geometry.coordinates; for ( var i = 0; i < lineStrings.length; i++) { var lineString = lineStrings[i]; - var dynamicObject = createObject(geojson, dynamicObjectCollection); + var dynamicObject = createObject(geojson, dataSource._dynamicObjectCollection); var positions = new Array(lineString.length); for ( var z = 0; z < lineString.length; z++) { positions[z] = crsFunction(lineString[z]); } - dynamicObject.merge(GeoJsonDataSource.defaultLine); + dynamicObject.merge(dataSource.defaultLine); dynamicObject.vertexPositions = new ConstantPositionProperty(positions); } } - function processMultiPoint(geojson, geometry, dynamicObjectCollection, crsFunction, source) { - var coordinates = geometry.coordinates; + function processPolygon(dataSource, geojson, geometry, crsFunction, source) { + var dynamicObject = createObject(geojson, dataSource._dynamicObjectCollection); + + //TODO Holes + var coordinates = geometry.coordinates[0]; + var positions = new Array(coordinates.length); for ( var i = 0; i < coordinates.length; i++) { - var dynamicObject = createObject(geojson, dynamicObjectCollection); - dynamicObject.merge(GeoJsonDataSource.defaultPoint); - dynamicObject.position = new ConstantPositionProperty(crsFunction(coordinates[i])); + positions[i] = crsFunction(coordinates[i]); } + dynamicObject.merge(dataSource.defaultPolygon); + dynamicObject.vertexPositions = new ConstantPositionProperty(positions); } - function processMultiPolygon(geojson, geometry, dynamicObjectCollection, crsFunction, source) { + function processMultiPolygon(dataSource, geojson, geometry, crsFunction, source) { var polygons = geometry.coordinates; for ( var i = 0; i < polygons.length; i++) { var polygon = polygons[i]; - var dynamicObject = createObject(geojson, dynamicObjectCollection); + var dynamicObject = createObject(geojson, dataSource._dynamicObjectCollection); //TODO holes var vertexPositions = polygon[0]; @@ -435,31 +448,12 @@ define(['../Core/createGuid', for ( var z = 0; z < vertexPositions.length; z++) { positions[z] = crsFunction(vertexPositions[z]); } - dynamicObject.merge(GeoJsonDataSource.defaultPolygon); + dynamicObject.merge(dataSource.defaultPolygon); dynamicObject.vertexPositions = new ConstantPositionProperty(positions); } } } - function processPoint(geojson, geometry, dynamicObjectCollection, crsFunction, source) { - var dynamicObject = createObject(geojson, dynamicObjectCollection); - dynamicObject.merge(GeoJsonDataSource.defaultPoint); - dynamicObject.position = new ConstantPositionProperty(crsFunction(geometry.coordinates)); - } - - function processPolygon(geojson, geometry, dynamicObjectCollection, crsFunction, source) { - var dynamicObject = createObject(geojson, dynamicObjectCollection); - - //TODO Holes - var coordinates = geometry.coordinates[0]; - var positions = new Array(coordinates.length); - for ( var i = 0; i < coordinates.length; i++) { - positions[i] = crsFunction(coordinates[i]); - } - dynamicObject.merge(GeoJsonDataSource.defaultPolygon); - dynamicObject.vertexPositions = new ConstantPositionProperty(positions); - } - var geoJsonObjectTypes = { Feature : processFeature, FeatureCollection : processFeatureCollection, diff --git a/Source/Widgets/Viewer/viewerDragDropMixin.js b/Source/Widgets/Viewer/viewerDragDropMixin.js index c8b2eefdd1ff..8b09307e9913 100644 --- a/Source/Widgets/Viewer/viewerDragDropMixin.js +++ b/Source/Widgets/Viewer/viewerDragDropMixin.js @@ -199,7 +199,7 @@ define([ } function createOnLoadCallback(viewer, source, firstTime) { - if (endsWith(source, "czml")) { + if (endsWith(source.toUpperCase(), "CZML")) { return function(evt) { var czmlSource = new CzmlDataSource(); try { @@ -219,7 +219,7 @@ define([ viewer.onDropError.raiseEvent(viewer, source, error); } }; - } else if (endsWith(source, 'geojson')) { + } else if (endsWith(source.toUpperCase(), 'GEOJSON')) { return function(evt) { var geoJsonSource = new GeoJsonDataSource(); try { From a0eb4e80c69a509d1cd7a9ad73d01edea724361e Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Thu, 20 Jun 2013 17:36:12 -0400 Subject: [PATCH 11/20] Doc cleanup. --- Source/DynamicScene/GeoJsonDataSource.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index fe8346ce2310..737e6545269e 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -59,11 +59,18 @@ define(['../Core/createGuid', * @alias GeoJsonDataSource * @constructor * - * @see http://www.geojson.org/. + * @see DataSourceDisplay + * @see GeoJSON specification. * * @example + * //Use a billboard instead of a point. * var dataSource = new GeoJsonDataSource(); - * dataSource.loadUrl('http://example.invalid/sample.geojson'); + * var defaultPoint = dataSource.defaulPoint; + * defaultPoint.point = undefined; + * var billboard = new DynamicBillboard(); + * billboard.image = new ConstantProperty('image.png'); + * defaultPoint.billboard = billboard; + * dataSource.loadUrl('sample.geojson'); */ var GeoJsonDataSource = function() { //default point @@ -110,21 +117,18 @@ define(['../Core/createGuid', /** * Gets or sets the default graphics to be applied to GeoJson Point and MultiPoint geometries. - * @memberof GeoJsonDataSource * @type DynamicObject */ this.defaultPoint = defaultPoint; /** * Gets or sets the default graphics to be applied to GeoJson LineString and MultiLineString geometries. - * @memberof GeoJsonDataSource * @type DynamicObject */ this.defaultLine = defaultLine; /** * Gets or sets the default graphics to be applied to GeoJson Polygon and MultiPolygon geometries. - * @memberof GeoJsonDataSource * @type DynamicObject */ this.defaultPolygon = defaultPolygon; From 08f5258637d222bfe03700a70e984f81f9227af6 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Fri, 21 Jun 2013 08:53:40 -0400 Subject: [PATCH 12/20] Update CHANGES --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 609fab72a171..f82227063305 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ Beta Releases * `ImageryProvider.loadImage` now requires that the calling imagery provider instance be passed as its first parameter. * Removed `CesiumViewerWidget` and replaced it with a new `Viewer` widget with mixin architecture. This new widget does not depend on Dojo and is part of the combined Cesium.js file. It is intended to be a flexible base widget for easily building robust applications. See [#838](https://github.com/AnalyticalGraphicsInc/cesium/pull/838) for the full details. * Removed the Dojo-based `checkForChromeFrame` function, and replaced it with a new standalone version that returns a promise to signal when the asynchronous check has completed. +* Added initial support for [GeoJSON](http://www.geojson.org/) see [#890](https://github.com/AnalyticalGraphicsInc/cesium/pull/890) for details. * Added `Context.getAntialias`. * Added rotation, aligned axis, width, and height properties to `Billboard`s. * Improved the performance of "missing tile" checking, especially for Bing imagery. From 67112b681ea4de529b3c4a0d368f2a3e80fc15ad Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Mon, 24 Jun 2013 11:42:27 -0400 Subject: [PATCH 13/20] Changes after review. --- Apps/CesiumViewer/CesiumViewer.js | 2 +- Source/DynamicScene/GeoJsonDataSource.js | 8 ++++---- Source/Widgets/Viewer/viewerDragDropMixin.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Apps/CesiumViewer/CesiumViewer.js b/Apps/CesiumViewer/CesiumViewer.js index 35916dfa9bf9..5af5989ac9f6 100644 --- a/Apps/CesiumViewer/CesiumViewer.js +++ b/Apps/CesiumViewer/CesiumViewer.js @@ -53,7 +53,7 @@ define([ }); function endsWith(str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; + return str.indexOf(suffix, -suffix.length) !== -1; } function startup() { diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index 737e6545269e..1a6f8e8e2ab1 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -76,7 +76,7 @@ define(['../Core/createGuid', //default point var defaultPoint = new DynamicObject('GeoJsonDataSource.defaultPoint'); var point = new DynamicPoint(); - point.color = new ConstantProperty(Color.BLUE); + point.color = new ConstantProperty(Color.CYAN); point.pixelSize = new ConstantProperty(10); point.outlineColor = new ConstantProperty(Color.BLACK); point.outlineWidth = new ConstantProperty(1); @@ -85,7 +85,7 @@ define(['../Core/createGuid', //default line var defaultLine = new DynamicObject('GeoJsonDataSource.defaultLine'); var polyline = new DynamicPolyline(); - polyline.color = new ConstantProperty(Color.BLUE); + polyline.color = new ConstantProperty(Color.CYAN); polyline.width = new ConstantProperty(2); polyline.outlineColor = new ConstantProperty(Color.BLACK); polyline.outlineWidth = new ConstantProperty(1); @@ -95,7 +95,7 @@ define(['../Core/createGuid', var defaultPolygon = new DynamicObject('GeoJsonDataSource.defaultPolygon'); var polygonMaterial = new DynamicMaterialProperty(); polyline = new DynamicPolyline(); - polyline.color = new ConstantProperty(Color.BLUE); + polyline.color = new ConstantProperty(Color.CYAN); polyline.width = new ConstantProperty(1); polyline.outlineColor = new ConstantProperty(Color.BLACK); polyline.outlineWidth = new ConstantProperty(0); @@ -105,7 +105,7 @@ define(['../Core/createGuid', polygonMaterial.processCzmlIntervals({ solidColor : { color : { - rgba : [0, 0, 255, 25.5] + rgba : [0, 255, 255, 25] } } }, undefined, undefined); diff --git a/Source/Widgets/Viewer/viewerDragDropMixin.js b/Source/Widgets/Viewer/viewerDragDropMixin.js index 8b09307e9913..f17df539d3db 100644 --- a/Source/Widgets/Viewer/viewerDragDropMixin.js +++ b/Source/Widgets/Viewer/viewerDragDropMixin.js @@ -195,7 +195,7 @@ define([ } function endsWith(str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; + return str.indexOf(suffix, -suffix.length) !== -1; } function createOnLoadCallback(viewer, source, firstTime) { From 14e48f108df6d298874b8933c5c6f412a6bf3f6e Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Mon, 24 Jun 2013 12:11:23 -0400 Subject: [PATCH 14/20] More changes. --- Apps/CesiumViewer/CesiumViewer.js | 6 ++++-- Source/DynamicScene/GeoJsonDataSource.js | 8 ++++---- Source/Widgets/Viewer/viewerDragDropMixin.js | 8 +++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Apps/CesiumViewer/CesiumViewer.js b/Apps/CesiumViewer/CesiumViewer.js index 5af5989ac9f6..82aa19e2a2b5 100644 --- a/Apps/CesiumViewer/CesiumViewer.js +++ b/Apps/CesiumViewer/CesiumViewer.js @@ -53,7 +53,9 @@ define([ }); function endsWith(str, suffix) { - return str.indexOf(suffix, -suffix.length) !== -1; + var strLength = str.length; + var suffixLength = suffix.length; + return (suffixLength < strLength) && (str.indexOf(suffix, strLength - suffixLength) !== -1); } function startup() { @@ -82,7 +84,7 @@ define([ if (typeof endUserOptions.source !== 'undefined') { var source; - if (endsWith(endUserOptions.source.toUpperCase(), "GEOJSON")) { + if (endsWith(endUserOptions.source.toUpperCase(), ".GEOJSON")) { source = new GeoJsonDataSource(); } else { source = new CzmlDataSource(); diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index 1a6f8e8e2ab1..7f52cde02712 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -76,7 +76,7 @@ define(['../Core/createGuid', //default point var defaultPoint = new DynamicObject('GeoJsonDataSource.defaultPoint'); var point = new DynamicPoint(); - point.color = new ConstantProperty(Color.CYAN); + point.color = new ConstantProperty(Color.YELLOW); point.pixelSize = new ConstantProperty(10); point.outlineColor = new ConstantProperty(Color.BLACK); point.outlineWidth = new ConstantProperty(1); @@ -85,7 +85,7 @@ define(['../Core/createGuid', //default line var defaultLine = new DynamicObject('GeoJsonDataSource.defaultLine'); var polyline = new DynamicPolyline(); - polyline.color = new ConstantProperty(Color.CYAN); + polyline.color = new ConstantProperty(Color.YELLOW); polyline.width = new ConstantProperty(2); polyline.outlineColor = new ConstantProperty(Color.BLACK); polyline.outlineWidth = new ConstantProperty(1); @@ -95,7 +95,7 @@ define(['../Core/createGuid', var defaultPolygon = new DynamicObject('GeoJsonDataSource.defaultPolygon'); var polygonMaterial = new DynamicMaterialProperty(); polyline = new DynamicPolyline(); - polyline.color = new ConstantProperty(Color.CYAN); + polyline.color = new ConstantProperty(Color.YELLOW); polyline.width = new ConstantProperty(1); polyline.outlineColor = new ConstantProperty(Color.BLACK); polyline.outlineWidth = new ConstantProperty(0); @@ -105,7 +105,7 @@ define(['../Core/createGuid', polygonMaterial.processCzmlIntervals({ solidColor : { color : { - rgba : [0, 255, 255, 25] + rgba : [255, 255, 0, 25] } } }, undefined, undefined); diff --git a/Source/Widgets/Viewer/viewerDragDropMixin.js b/Source/Widgets/Viewer/viewerDragDropMixin.js index f17df539d3db..81010764b557 100644 --- a/Source/Widgets/Viewer/viewerDragDropMixin.js +++ b/Source/Widgets/Viewer/viewerDragDropMixin.js @@ -195,11 +195,13 @@ define([ } function endsWith(str, suffix) { - return str.indexOf(suffix, -suffix.length) !== -1; + var strLength = str.length; + var suffixLength = suffix.length; + return (suffixLength < strLength) && (str.indexOf(suffix, strLength - suffixLength) !== -1); } function createOnLoadCallback(viewer, source, firstTime) { - if (endsWith(source.toUpperCase(), "CZML")) { + if (endsWith(source.toUpperCase(), ".CZML")) { return function(evt) { var czmlSource = new CzmlDataSource(); try { @@ -219,7 +221,7 @@ define([ viewer.onDropError.raiseEvent(viewer, source, error); } }; - } else if (endsWith(source.toUpperCase(), 'GEOJSON')) { + } else if (endsWith(source.toUpperCase(), '.GEOJSON')) { return function(evt) { var geoJsonSource = new GeoJsonDataSource(); try { From 4b418a67763548ccabe35d7243e3f7e5615603af Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Mon, 24 Jun 2013 13:47:52 -0400 Subject: [PATCH 15/20] Minor tweak to ConstantProperty. --- Source/DynamicScene/ConstantProperty.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/DynamicScene/ConstantProperty.js b/Source/DynamicScene/ConstantProperty.js index cc5c49d3316a..5f068cf3511e 100644 --- a/Source/DynamicScene/ConstantProperty.js +++ b/Source/DynamicScene/ConstantProperty.js @@ -12,19 +12,20 @@ define(function() { */ var ConstantProperty = function(value) { this._value = value; + this._clonable = typeof value !== 'undefined' && typeof value.clone === 'function'; }; /** - * Returns the value of the property. + * Gets the value of the property, optionally cloning it. * @memberof ConstantProperty * - * @param {JulianDate} time The time for which to retrieve the value. This value is not used. - * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned. - * @returns The modified result parameter or a new instance if the result parameter was not supplied. + * @param {JulianDate} time The time for which to retrieve the value. This parameter is unused. + * @param {Object} [result] The object to store the value into if the value is clonable. If the result is omitted or the value does not implement clone, the actual value is returned. + * @returns The modified result parameter or the actual value instance if the value is not clonable. */ ConstantProperty.prototype.getValue = function(time, result) { var value = this._value; - if (typeof value !== 'undefined' && typeof value.clone === 'function') { + if (this._clonable) { return value.clone(result); } return value; From 4d7d09fb65c69304a5128810d142faa7176aee8c Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Mon, 24 Jun 2013 13:54:23 -0400 Subject: [PATCH 16/20] Change from DeveloperError to RuntimeError for actual GeoJSON processing. --- Source/DynamicScene/GeoJsonDataSource.js | 28 +++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index 7f52cde02712..37db9ba7e71d 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -4,6 +4,7 @@ define(['../Core/createGuid', '../Core/Color', '../Core/defineProperties', '../Core/DeveloperError', + '../Core/RuntimeError', '../Core/Ellipsoid', '../Core/Event', '../Core/loadJson', @@ -20,6 +21,7 @@ define(['../Core/createGuid', Color, defineProperties, DeveloperError, + RuntimeError, Ellipsoid, Event, loadJson, @@ -216,11 +218,11 @@ define(['../Core/createGuid', * * @exception {DeveloperError} geoJson is required. * @exception {DeveloperError} Unsupported GeoJSON object type. - * @exception {DeveloperError} crs is null. - * @exception {DeveloperError} crs.properties is undefined. - * @exception {DeveloperError} Unknown crs name. - * @exception {DeveloperError} Unable to resolve crs link. - * @exception {DeveloperError} Unknown crs type. + * @exception {RuntimeError} crs is null. + * @exception {RuntimeError} crs.properties is undefined. + * @exception {RuntimeError} Unknown crs name. + * @exception {RuntimeError} Unable to resolve crs link. + * @exception {RuntimeError} Unknown crs type. */ GeoJsonDataSource.prototype.load = function(geoJson, source) { if (typeof geoJson === 'undefined') { @@ -237,17 +239,17 @@ define(['../Core/createGuid', var crs = geoJson.crs; if (typeof crs !== 'undefined') { if (crs === null) { - throw new DeveloperError('crs is null.'); + throw new RuntimeError('crs is null.'); } if (typeof crs.properties === 'undefined') { - throw new DeveloperError('crs.properties is undefined.'); + throw new RuntimeError('crs.properties is undefined.'); } var properties = crs.properties; if (crs.type === 'name') { var crsFunction = GeoJsonDataSource.crsNames[properties.name]; if (typeof crsFunction === 'undefined') { - throw new DeveloperError('Unknown crs name: ' + properties.name); + throw new RuntimeError('Unknown crs name: ' + properties.name); } crsPromise = when(crsFunction, function(crsFunction) { @@ -262,12 +264,12 @@ define(['../Core/createGuid', } if (typeof handler === 'undefined') { - throw new DeveloperError('Unable to resolve crs link: ' + JSON.stringify(properties)); + throw new RuntimeError('Unable to resolve crs link: ' + JSON.stringify(properties)); } crsPromise = handler(properties); } else { - throw new DeveloperError('Unknown crs type: ' + crs.type); + throw new RuntimeError('Unknown crs type: ' + crs.type); } } else { //Use the default @@ -349,7 +351,7 @@ define(['../Core/createGuid', // GeoJson processing functions function processFeature(dataSource, feature, notUsed, crsFunction, source) { if (typeof feature.geometry === 'undefined') { - throw new DeveloperError('feature.geometry is required.'); + throw new RuntimeError('feature.geometry is required.'); } if (feature.geometry === null) { @@ -359,7 +361,7 @@ define(['../Core/createGuid', var geometryType = feature.geometry.type; var geometryHandler = geometryTypes[geometryType]; if (typeof geometryHandler === 'undefined') { - throw new DeveloperError('Unknown geometry type: ' + geometryType); + throw new RuntimeError('Unknown geometry type: ' + geometryType); } geometryHandler(dataSource, feature, feature.geometry, crsFunction, source); } @@ -379,7 +381,7 @@ define(['../Core/createGuid', var geometryType = geometry.type; var geometryHandler = geometryTypes[geometryType]; if (typeof geometryHandler === 'undefined') { - throw new DeveloperError('Unknown geometry type: ' + geometryType); + throw new RuntimeError('Unknown geometry type: ' + geometryType); } geometryHandler(dataSource, geoJson, geometry, crsFunction, source); } From 54ae0eacdcb26500696b04ba84a3b899a05a4418 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Mon, 24 Jun 2013 14:16:48 -0400 Subject: [PATCH 17/20] Cleanup promise usage. --- Source/DynamicScene/GeoJsonDataSource.js | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index 37db9ba7e71d..df359c6ce4be 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -235,7 +235,7 @@ define(['../Core/createGuid', } //Check for a Coordinate Reference System. - var crsPromise; + var crsFunction = defaultCrsFunction; var crs = geoJson.crs; if (typeof crs !== 'undefined') { if (crs === null) { @@ -247,16 +247,10 @@ define(['../Core/createGuid', var properties = crs.properties; if (crs.type === 'name') { - var crsFunction = GeoJsonDataSource.crsNames[properties.name]; + crsFunction = GeoJsonDataSource.crsNames[properties.name]; if (typeof crsFunction === 'undefined') { throw new RuntimeError('Unknown crs name: ' + properties.name); } - - crsPromise = when(crsFunction, function(crsFunction) { - var deferred = when.defer(); - deferred.resolve(crsFunction); - return deferred.promise; - }); } else if (crs.type === 'link') { var handler = GeoJsonDataSource.crsLinkHrefs[properties.href]; if (typeof handler === 'undefined') { @@ -267,23 +261,16 @@ define(['../Core/createGuid', throw new RuntimeError('Unable to resolve crs link: ' + JSON.stringify(properties)); } - crsPromise = handler(properties); + crsFunction = handler(properties); } else { throw new RuntimeError('Unknown crs type: ' + crs.type); } - } else { - //Use the default - crsPromise = when(defaultCrsFunction, function(defaultCrsFunction) { - var deferred = when.defer(); - deferred.resolve(defaultCrsFunction); - return deferred.promise; - }); } this._dynamicObjectCollection.clear(); var that = this; - return crsPromise.then(function(crsFunction) { + return when(crsFunction, function(crsFunction) { typeHandler(that, geoJson, geoJson, crsFunction, source); that._changed.raiseEvent(that); }); From bb14a2b15693b8a97e760471fc3fb8b1d2618fcf Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Mon, 24 Jun 2013 15:11:07 -0400 Subject: [PATCH 18/20] Tweak to DynamicObject.merge. --- Source/DynamicScene/DynamicObject.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Source/DynamicScene/DynamicObject.js b/Source/DynamicScene/DynamicObject.js index 267594e2f136..9b5cbc34efe3 100644 --- a/Source/DynamicScene/DynamicObject.js +++ b/Source/DynamicScene/DynamicObject.js @@ -204,9 +204,7 @@ define([ } for ( var property in other) { if (other.hasOwnProperty(property)) { - if (this.hasOwnProperty(property) && (typeof this[property] === 'undefined')) { - this[property] = other[property]; - } + this[property] = defaultValue(this[property], other[property]); } } }; From 501036994a368ab447967d7f0f202d8d57c55486 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Mon, 24 Jun 2013 16:13:53 -0400 Subject: [PATCH 19/20] Standardize casing. 1. GeoJSON is the format name. 2. geojson is the extension we use. 3. geoJson is the identifier casing. --- Source/DynamicScene/GeoJsonDataSource.js | 50 ++++++++++----------- Specs/DynamicScene/GeoJsonDataSourceSpec.js | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index df359c6ce4be..4985fbac9671 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -118,19 +118,19 @@ define(['../Core/createGuid', this._dynamicObjectCollection = new DynamicObjectCollection(); /** - * Gets or sets the default graphics to be applied to GeoJson Point and MultiPoint geometries. + * Gets or sets the default graphics to be applied to GeoJSON Point and MultiPoint geometries. * @type DynamicObject */ this.defaultPoint = defaultPoint; /** - * Gets or sets the default graphics to be applied to GeoJson LineString and MultiLineString geometries. + * Gets or sets the default graphics to be applied to GeoJSON LineString and MultiLineString geometries. * @type DynamicObject */ this.defaultLine = defaultLine; /** - * Gets or sets the default graphics to be applied to GeoJson Polygon and MultiPolygon geometries. + * Gets or sets the default graphics to be applied to GeoJSON Polygon and MultiPolygon geometries. * @type DynamicObject */ this.defaultPolygon = defaultPolygon; @@ -158,7 +158,7 @@ define(['../Core/createGuid', }; /** - * Since GeoJson is a static format, this function always returns undefined. + * Since GeoJSON is a static format, this function always returns undefined. * @memberof GeoJsonDataSource */ GeoJsonDataSource.prototype.getClock = function() { @@ -283,7 +283,7 @@ define(['../Core/createGuid', /** * An object that maps the name of a crs to a callback function - * which takes a GeoJson coordinate and transforms it into a + * which takes a GeoJSON coordinate and transforms it into a * WGS84 Earth-fixed Cartesian. * @memberof GeoJsonDataSource * @type Object @@ -296,7 +296,7 @@ define(['../Core/createGuid', /** * An object that maps the href property of a crs link to a callback function * which takes the crs properties object and returns a Promise that resolves - * to a function that takes a GeoJson coordinate and transforms it into a WGS84 Earth-fixed Cartesian. + * to a function that takes a GeoJSON coordinate and transforms it into a WGS84 Earth-fixed Cartesian. * Items in this object take precedence over those defined in crsLinkHrefs, assuming * the link has a type specified. * @memberof GeoJsonDataSource @@ -307,19 +307,19 @@ define(['../Core/createGuid', /** * An object that maps the type property of a crs link to a callback function * which takes the crs properties object and returns a Promise that resolves - * to a function that takes a GeoJson coordinate and transforms it into a WGS84 Earth-fixed Cartesian. + * to a function that takes a GeoJSON coordinate and transforms it into a WGS84 Earth-fixed Cartesian. * Items in crsLinkHrefs take precedence over this object. * @memberof GeoJsonDataSource * @type Object */ GeoJsonDataSource.crsLinkTypes = {}; - //GeoJson specifies only the Feature object has a usable id property + //GeoJSON specifies only the Feature object has a usable id property //But since "multi" geometries create multiple dynamicObject, //we can't use it for them either. - function createObject(geojson, dynamicObjectCollection) { - var id = geojson.id; - if (typeof id === 'undefined' || geojson.type !== 'Feature') { + function createObject(geoJson, dynamicObjectCollection) { + var id = geoJson.id; + if (typeof id === 'undefined' || geoJson.type !== 'Feature') { id = createGuid(); } else { var i = 2; @@ -331,11 +331,11 @@ define(['../Core/createGuid', id = finalId; } var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); - dynamicObject.geoJson = geojson; + dynamicObject.geoJson = geoJson; return dynamicObject; } - // GeoJson processing functions + // GeoJSON processing functions function processFeature(dataSource, feature, notUsed, crsFunction, source) { if (typeof feature.geometry === 'undefined') { throw new RuntimeError('feature.geometry is required.'); @@ -374,23 +374,23 @@ define(['../Core/createGuid', } } - function processPoint(dataSource, geojson, geometry, crsFunction, source) { - var dynamicObject = createObject(geojson, dataSource._dynamicObjectCollection); + function processPoint(dataSource, geoJson, geometry, crsFunction, source) { + var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); dynamicObject.merge(dataSource.defaultPoint); dynamicObject.position = new ConstantPositionProperty(crsFunction(geometry.coordinates)); } - function processMultiPoint(dataSource, geojson, geometry, crsFunction, source) { + function processMultiPoint(dataSource, geoJson, geometry, crsFunction, source) { var coordinates = geometry.coordinates; for ( var i = 0; i < coordinates.length; i++) { - var dynamicObject = createObject(geojson, dataSource._dynamicObjectCollection); + var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); dynamicObject.merge(dataSource.defaultPoint); dynamicObject.position = new ConstantPositionProperty(crsFunction(coordinates[i])); } } - function processLineString(dataSource, geojson, geometry, crsFunction, source) { - var dynamicObject = createObject(geojson, dataSource._dynamicObjectCollection); + function processLineString(dataSource, geoJson, geometry, crsFunction, source) { + var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); var coordinates = geometry.coordinates; var positions = new Array(coordinates.length); @@ -401,11 +401,11 @@ define(['../Core/createGuid', dynamicObject.vertexPositions = new ConstantPositionProperty(positions); } - function processMultiLineString(dataSource, geojson, geometry, crsFunction, source) { + function processMultiLineString(dataSource, geoJson, geometry, crsFunction, source) { var lineStrings = geometry.coordinates; for ( var i = 0; i < lineStrings.length; i++) { var lineString = lineStrings[i]; - var dynamicObject = createObject(geojson, dataSource._dynamicObjectCollection); + var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); var positions = new Array(lineString.length); for ( var z = 0; z < lineString.length; z++) { positions[z] = crsFunction(lineString[z]); @@ -415,8 +415,8 @@ define(['../Core/createGuid', } } - function processPolygon(dataSource, geojson, geometry, crsFunction, source) { - var dynamicObject = createObject(geojson, dataSource._dynamicObjectCollection); + function processPolygon(dataSource, geoJson, geometry, crsFunction, source) { + var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); //TODO Holes var coordinates = geometry.coordinates[0]; @@ -428,11 +428,11 @@ define(['../Core/createGuid', dynamicObject.vertexPositions = new ConstantPositionProperty(positions); } - function processMultiPolygon(dataSource, geojson, geometry, crsFunction, source) { + function processMultiPolygon(dataSource, geoJson, geometry, crsFunction, source) { var polygons = geometry.coordinates; for ( var i = 0; i < polygons.length; i++) { var polygon = polygons[i]; - var dynamicObject = createObject(geojson, dataSource._dynamicObjectCollection); + var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); //TODO holes var vertexPositions = polygon[0]; diff --git a/Specs/DynamicScene/GeoJsonDataSourceSpec.js b/Specs/DynamicScene/GeoJsonDataSourceSpec.js index 0a72438a9cb3..e1682fc32542 100644 --- a/Specs/DynamicScene/GeoJsonDataSourceSpec.js +++ b/Specs/DynamicScene/GeoJsonDataSourceSpec.js @@ -447,7 +447,7 @@ defineSuite(['DynamicScene/GeoJsonDataSource', }); }); - it('load throws with undefined geojson', function() { + it('load throws with undefined geoJson', function() { var dataSource = new GeoJsonDataSource(); expect(function() { dataSource.load(undefined); From 490a8f2779fd7c3e06d5f7ebcff3d317dbc26291 Mon Sep 17 00:00:00 2001 From: Matthew Amato Date: Mon, 24 Jun 2013 16:50:31 -0400 Subject: [PATCH 20/20] More changes after review. --- Source/DynamicScene/GeoJsonDataSource.js | 295 +++++++++---------- Source/Widgets/Viewer/viewerDragDropMixin.js | 47 ++- 2 files changed, 161 insertions(+), 181 deletions(-) diff --git a/Source/DynamicScene/GeoJsonDataSource.js b/Source/DynamicScene/GeoJsonDataSource.js index 4985fbac9671..9335b5dea669 100644 --- a/Source/DynamicScene/GeoJsonDataSource.js +++ b/Source/DynamicScene/GeoJsonDataSource.js @@ -35,7 +35,7 @@ define(['../Core/createGuid', when) { "use strict"; - //DynamicPosiionProperty is pretty hard to use with non-CZML based data + //DynamicPositionProperty is pretty hard to use with non-CZML based data //For now we create two of our own properties for exposing GeoJSON //data. var ConstantPositionProperty = function(value) { @@ -54,6 +54,144 @@ define(['../Core/createGuid', this._value = value; }; + //GeoJSON specifies only the Feature object has a usable id property + //But since "multi" geometries create multiple dynamicObject, + //we can't use it for them either. + function createObject(geoJson, dynamicObjectCollection) { + var id = geoJson.id; + if (typeof id === 'undefined' || geoJson.type !== 'Feature') { + id = createGuid(); + } else { + var i = 2; + var finalId = id; + while (typeof dynamicObjectCollection.getObject(finalId) !== 'undefined') { + finalId = id + "_" + i; + i++; + } + id = finalId; + } + var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); + dynamicObject.geoJson = geoJson; + return dynamicObject; + } + + function coordinatesArrayToCartesianArray(coordinates, crsFunction) { + var positions = new Array(coordinates.length); + for ( var i = 0; i < coordinates.length; i++) { + positions[i] = crsFunction(coordinates[i]); + } + return positions; + } + + // GeoJSON processing functions + function processFeature(dataSource, feature, notUsed, crsFunction, source) { + if (typeof feature.geometry === 'undefined') { + throw new RuntimeError('feature.geometry is required.'); + } + + if (feature.geometry === null) { + //Null geometry is allowed, so just create an empty dynamicObject instance for it. + createObject(feature, dataSource._dynamicObjectCollection); + } else { + var geometryType = feature.geometry.type; + var geometryHandler = geometryTypes[geometryType]; + if (typeof geometryHandler === 'undefined') { + throw new RuntimeError('Unknown geometry type: ' + geometryType); + } + geometryHandler(dataSource, feature, feature.geometry, crsFunction, source); + } + } + + function processFeatureCollection(dataSource, featureCollection, notUsed, crsFunction, source) { + var features = featureCollection.features; + for ( var i = 0, len = features.length; i < len; i++) { + processFeature(dataSource, features[i], undefined, crsFunction, source); + } + } + + function processGeometryCollection(dataSource, geoJson, geometryCollection, crsFunction, source) { + var geometries = geometryCollection.geometries; + for ( var i = 0, len = geometries.length; i < len; i++) { + var geometry = geometries[i]; + var geometryType = geometry.type; + var geometryHandler = geometryTypes[geometryType]; + if (typeof geometryHandler === 'undefined') { + throw new RuntimeError('Unknown geometry type: ' + geometryType); + } + geometryHandler(dataSource, geoJson, geometry, crsFunction, source); + } + } + + function processPoint(dataSource, geoJson, geometry, crsFunction, source) { + var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); + dynamicObject.merge(dataSource.defaultPoint); + dynamicObject.position = new ConstantPositionProperty(crsFunction(geometry.coordinates)); + } + + function processMultiPoint(dataSource, geoJson, geometry, crsFunction, source) { + var coordinates = geometry.coordinates; + for ( var i = 0; i < coordinates.length; i++) { + var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); + dynamicObject.merge(dataSource.defaultPoint); + dynamicObject.position = new ConstantPositionProperty(crsFunction(coordinates[i])); + } + } + + function processLineString(dataSource, geoJson, geometry, crsFunction, source) { + var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); + dynamicObject.merge(dataSource.defaultLine); + dynamicObject.vertexPositions = new ConstantPositionProperty(coordinatesArrayToCartesianArray(geometry.coordinates, crsFunction)); + } + + function processMultiLineString(dataSource, geoJson, geometry, crsFunction, source) { + var lineStrings = geometry.coordinates; + for ( var i = 0; i < lineStrings.length; i++) { + var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); + dynamicObject.merge(dataSource.defaultLine); + dynamicObject.vertexPositions = new ConstantPositionProperty(coordinatesArrayToCartesianArray(lineStrings[i], crsFunction)); + } + } + + function processPolygon(dataSource, geoJson, geometry, crsFunction, source) { + //TODO Holes + var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); + dynamicObject.merge(dataSource.defaultPolygon); + dynamicObject.vertexPositions = new ConstantPositionProperty(coordinatesArrayToCartesianArray(geometry.coordinates[0], crsFunction)); + } + + function processMultiPolygon(dataSource, geoJson, geometry, crsFunction, source) { + //TODO holes + var polygons = geometry.coordinates; + for ( var i = 0; i < polygons.length; i++) { + var polygon = polygons[i]; + var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); + dynamicObject.merge(dataSource.defaultPolygon); + dynamicObject.vertexPositions = new ConstantPositionProperty(coordinatesArrayToCartesianArray(polygon[0], crsFunction)); + } + } + + var geoJsonObjectTypes = { + Feature : processFeature, + FeatureCollection : processFeatureCollection, + GeometryCollection : processGeometryCollection, + LineString : processLineString, + MultiLineString : processMultiLineString, + MultiPoint : processMultiPoint, + MultiPolygon : processMultiPolygon, + Point : processPoint, + Polygon : processPolygon + }; + + var geometryTypes = { + GeometryCollection : processGeometryCollection, + LineString : processLineString, + MultiLineString : processMultiLineString, + MultiPoint : processMultiPoint, + MultiPolygon : processMultiPolygon, + Point : processPoint, + Polygon : processPolygon + }; + /** * A {@link DataSource} which processes GeoJSON. Since GeoJSON has no standard for styling content, * we provide default graphics via the defaultPoint, defaultLine, and defaultPolygon properties. @@ -314,160 +452,5 @@ define(['../Core/createGuid', */ GeoJsonDataSource.crsLinkTypes = {}; - //GeoJSON specifies only the Feature object has a usable id property - //But since "multi" geometries create multiple dynamicObject, - //we can't use it for them either. - function createObject(geoJson, dynamicObjectCollection) { - var id = geoJson.id; - if (typeof id === 'undefined' || geoJson.type !== 'Feature') { - id = createGuid(); - } else { - var i = 2; - var finalId = id; - while (typeof dynamicObjectCollection.getObject(finalId) !== 'undefined') { - finalId = id + "_" + i; - i++; - } - id = finalId; - } - var dynamicObject = dynamicObjectCollection.getOrCreateObject(id); - dynamicObject.geoJson = geoJson; - return dynamicObject; - } - - // GeoJSON processing functions - function processFeature(dataSource, feature, notUsed, crsFunction, source) { - if (typeof feature.geometry === 'undefined') { - throw new RuntimeError('feature.geometry is required.'); - } - - if (feature.geometry === null) { - //Null geometry is allowed, so just create an empty dynamicObject instance for it. - createObject(feature, dataSource._dynamicObjectCollection); - } else { - var geometryType = feature.geometry.type; - var geometryHandler = geometryTypes[geometryType]; - if (typeof geometryHandler === 'undefined') { - throw new RuntimeError('Unknown geometry type: ' + geometryType); - } - geometryHandler(dataSource, feature, feature.geometry, crsFunction, source); - } - } - - function processFeatureCollection(dataSource, featureCollection, notUsed, crsFunction, source) { - var features = featureCollection.features; - for ( var i = 0, len = features.length; i < len; i++) { - processFeature(dataSource, features[i], undefined, crsFunction, source); - } - } - - function processGeometryCollection(dataSource, geoJson, geometryCollection, crsFunction, source) { - var geometries = geometryCollection.geometries; - for ( var i = 0, len = geometries.length; i < len; i++) { - var geometry = geometries[i]; - var geometryType = geometry.type; - var geometryHandler = geometryTypes[geometryType]; - if (typeof geometryHandler === 'undefined') { - throw new RuntimeError('Unknown geometry type: ' + geometryType); - } - geometryHandler(dataSource, geoJson, geometry, crsFunction, source); - } - } - - function processPoint(dataSource, geoJson, geometry, crsFunction, source) { - var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); - dynamicObject.merge(dataSource.defaultPoint); - dynamicObject.position = new ConstantPositionProperty(crsFunction(geometry.coordinates)); - } - - function processMultiPoint(dataSource, geoJson, geometry, crsFunction, source) { - var coordinates = geometry.coordinates; - for ( var i = 0; i < coordinates.length; i++) { - var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); - dynamicObject.merge(dataSource.defaultPoint); - dynamicObject.position = new ConstantPositionProperty(crsFunction(coordinates[i])); - } - } - - function processLineString(dataSource, geoJson, geometry, crsFunction, source) { - var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); - - var coordinates = geometry.coordinates; - var positions = new Array(coordinates.length); - for ( var i = 0; i < coordinates.length; i++) { - positions[i] = crsFunction(coordinates[i]); - } - dynamicObject.merge(dataSource.defaultLine); - dynamicObject.vertexPositions = new ConstantPositionProperty(positions); - } - - function processMultiLineString(dataSource, geoJson, geometry, crsFunction, source) { - var lineStrings = geometry.coordinates; - for ( var i = 0; i < lineStrings.length; i++) { - var lineString = lineStrings[i]; - var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); - var positions = new Array(lineString.length); - for ( var z = 0; z < lineString.length; z++) { - positions[z] = crsFunction(lineString[z]); - } - dynamicObject.merge(dataSource.defaultLine); - dynamicObject.vertexPositions = new ConstantPositionProperty(positions); - } - } - - function processPolygon(dataSource, geoJson, geometry, crsFunction, source) { - var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); - - //TODO Holes - var coordinates = geometry.coordinates[0]; - var positions = new Array(coordinates.length); - for ( var i = 0; i < coordinates.length; i++) { - positions[i] = crsFunction(coordinates[i]); - } - dynamicObject.merge(dataSource.defaultPolygon); - dynamicObject.vertexPositions = new ConstantPositionProperty(positions); - } - - function processMultiPolygon(dataSource, geoJson, geometry, crsFunction, source) { - var polygons = geometry.coordinates; - for ( var i = 0; i < polygons.length; i++) { - var polygon = polygons[i]; - var dynamicObject = createObject(geoJson, dataSource._dynamicObjectCollection); - - //TODO holes - var vertexPositions = polygon[0]; - for ( var q = 0; q < vertexPositions.length; q++) { - var positions = new Array(vertexPositions.length); - for ( var z = 0; z < vertexPositions.length; z++) { - positions[z] = crsFunction(vertexPositions[z]); - } - dynamicObject.merge(dataSource.defaultPolygon); - dynamicObject.vertexPositions = new ConstantPositionProperty(positions); - } - } - } - - var geoJsonObjectTypes = { - Feature : processFeature, - FeatureCollection : processFeatureCollection, - GeometryCollection : processGeometryCollection, - LineString : processLineString, - MultiLineString : processMultiLineString, - MultiPoint : processMultiPoint, - MultiPolygon : processMultiPolygon, - Point : processPoint, - Polygon : processPolygon - }; - - var geometryTypes = { - GeometryCollection : processGeometryCollection, - LineString : processLineString, - MultiLineString : processMultiLineString, - MultiPoint : processMultiPoint, - MultiPolygon : processMultiPolygon, - Point : processPoint, - Polygon : processPolygon - }; - return GeoJsonDataSource; }); \ No newline at end of file diff --git a/Source/Widgets/Viewer/viewerDragDropMixin.js b/Source/Widgets/Viewer/viewerDragDropMixin.js index 81010764b557..1739284b3ec4 100644 --- a/Source/Widgets/Viewer/viewerDragDropMixin.js +++ b/Source/Widgets/Viewer/viewerDragDropMixin.js @@ -7,6 +7,7 @@ define([ '../../Core/wrapFunction', '../../DynamicScene/CzmlDataSource', '../../DynamicScene/GeoJsonDataSource', + '../../ThirdParty/when', '../getElement' ], function( defaultValue, @@ -16,6 +17,7 @@ define([ wrapFunction, CzmlDataSource, GeoJsonDataSource, + when, getElement) { "use strict"; /*global console*/ @@ -201,14 +203,22 @@ define([ } function createOnLoadCallback(viewer, source, firstTime) { + var DataSource; if (endsWith(source.toUpperCase(), ".CZML")) { - return function(evt) { - var czmlSource = new CzmlDataSource(); - try { - czmlSource.load(JSON.parse(evt.target.result), source); - viewer.dataSources.add(czmlSource); + DataSource = CzmlDataSource; + } else if (endsWith(source.toUpperCase(), '.GEOJSON')) { + DataSource = GeoJsonDataSource; + } else { + viewer.onDropError.raiseEvent(viewer, source, 'Unrecognized file extension: ' + source); + } + + return function(evt) { + var dataSource = new DataSource(); + try { + when(dataSource.load(JSON.parse(evt.target.result), source), function() { + viewer.dataSources.add(dataSource); if (firstTime) { - var dataClock = czmlSource.getClock(); + var dataClock = dataSource.getClock(); if (typeof dataClock !== 'undefined') { dataClock.clone(viewer.clock); if (typeof viewer.timeline !== 'undefined') { @@ -217,26 +227,13 @@ define([ } } } - } catch (error) { + }, function(error) { viewer.onDropError.raiseEvent(viewer, source, error); - } - }; - } else if (endsWith(source.toUpperCase(), '.GEOJSON')) { - return function(evt) { - var geoJsonSource = new GeoJsonDataSource(); - try { - geoJsonSource.load(JSON.parse(evt.target.result), source).then(function() { - viewer.dataSources.add(geoJsonSource); - }, function(error) { - viewer.onDropError.raiseEvent(viewer, source, error); - }); - } catch (error) { - viewer.onDropError.raiseEvent(viewer, source, error); - } - }; - } else { - viewer.onDropError.raiseEvent(viewer, source, 'Unrecognized file extension: ' + source); - } + }); + } catch (error) { + viewer.onDropError.raiseEvent(viewer, source, error); + } + }; } function createOnDropErrorCallback(viewer, name) {