Source: DataSources/GeoJsonDataSource.js

  1. /*global define*/
  2. define([
  3. '../Core/Cartesian3',
  4. '../Core/Color',
  5. '../Core/createGuid',
  6. '../Core/defaultValue',
  7. '../Core/defined',
  8. '../Core/defineProperties',
  9. '../Core/DeveloperError',
  10. '../Core/Event',
  11. '../Core/getFilenameFromUri',
  12. '../Core/loadJson',
  13. '../Core/PinBuilder',
  14. '../Core/PolygonHierarchy',
  15. '../Core/RuntimeError',
  16. '../Scene/HeightReference',
  17. '../Scene/VerticalOrigin',
  18. '../ThirdParty/topojson',
  19. '../ThirdParty/when',
  20. './BillboardGraphics',
  21. './CallbackProperty',
  22. './ColorMaterialProperty',
  23. './ConstantPositionProperty',
  24. './ConstantProperty',
  25. './CorridorGraphics',
  26. './DataSource',
  27. './EntityCluster',
  28. './EntityCollection',
  29. './PolygonGraphics',
  30. './PolylineGraphics'
  31. ], function(
  32. Cartesian3,
  33. Color,
  34. createGuid,
  35. defaultValue,
  36. defined,
  37. defineProperties,
  38. DeveloperError,
  39. Event,
  40. getFilenameFromUri,
  41. loadJson,
  42. PinBuilder,
  43. PolygonHierarchy,
  44. RuntimeError,
  45. HeightReference,
  46. VerticalOrigin,
  47. topojson,
  48. when,
  49. BillboardGraphics,
  50. CallbackProperty,
  51. ColorMaterialProperty,
  52. ConstantPositionProperty,
  53. ConstantProperty,
  54. CorridorGraphics,
  55. DataSource,
  56. EntityCluster,
  57. EntityCollection,
  58. PolygonGraphics,
  59. PolylineGraphics) {
  60. 'use strict';
  61. function defaultCrsFunction(coordinates) {
  62. return Cartesian3.fromDegrees(coordinates[0], coordinates[1], coordinates[2]);
  63. }
  64. var crsNames = {
  65. 'urn:ogc:def:crs:OGC:1.3:CRS84' : defaultCrsFunction,
  66. 'EPSG:4326' : defaultCrsFunction,
  67. 'urn:ogc:def:crs:EPSG::4326' : defaultCrsFunction
  68. };
  69. var crsLinkHrefs = {};
  70. var crsLinkTypes = {};
  71. var defaultMarkerSize = 48;
  72. var defaultMarkerSymbol;
  73. var defaultMarkerColor = Color.ROYALBLUE;
  74. var defaultStroke = Color.YELLOW;
  75. var defaultStrokeWidth = 2;
  76. var defaultFill = Color.fromBytes(255, 255, 0, 100);
  77. var defaultClampToGround = false;
  78. var defaultStrokeWidthProperty = new ConstantProperty(defaultStrokeWidth);
  79. var defaultStrokeMaterialProperty = new ColorMaterialProperty(defaultStroke);
  80. var defaultFillMaterialProperty = new ColorMaterialProperty(defaultFill);
  81. var sizes = {
  82. small : 24,
  83. medium : 48,
  84. large : 64
  85. };
  86. var simpleStyleIdentifiers = ['title', 'description', //
  87. 'marker-size', 'marker-symbol', 'marker-color', 'stroke', //
  88. 'stroke-opacity', 'stroke-width', 'fill', 'fill-opacity'];
  89. function defaultDescribe(properties, nameProperty) {
  90. var html = '';
  91. for ( var key in properties) {
  92. if (properties.hasOwnProperty(key)) {
  93. if (key === nameProperty || simpleStyleIdentifiers.indexOf(key) !== -1) {
  94. continue;
  95. }
  96. var value = properties[key];
  97. if (defined(value)) {
  98. if (typeof value === 'object') {
  99. html += '<tr><th>' + key + '</th><td>' + defaultDescribe(value) + '</td></tr>';
  100. } else {
  101. html += '<tr><th>' + key + '</th><td>' + value + '</td></tr>';
  102. }
  103. }
  104. }
  105. }
  106. if (html.length > 0) {
  107. html = '<table class="cesium-infoBox-defaultTable"><tbody>' + html + '</tbody></table>';
  108. }
  109. return html;
  110. }
  111. function createDescriptionCallback(describe, properties, nameProperty) {
  112. var description;
  113. return function(time, result) {
  114. if (!defined(description)) {
  115. description = describe(properties, nameProperty);
  116. }
  117. return description;
  118. };
  119. }
  120. function defaultDescribeProperty(properties, nameProperty) {
  121. return new CallbackProperty(createDescriptionCallback(defaultDescribe, properties, nameProperty), true);
  122. }
  123. //GeoJSON specifies only the Feature object has a usable id property
  124. //But since "multi" geometries create multiple entity,
  125. //we can't use it for them either.
  126. function createObject(geoJson, entityCollection, describe) {
  127. var id = geoJson.id;
  128. if (!defined(id) || geoJson.type !== 'Feature') {
  129. id = createGuid();
  130. } else {
  131. var i = 2;
  132. var finalId = id;
  133. while (defined(entityCollection.getById(finalId))) {
  134. finalId = id + "_" + i;
  135. i++;
  136. }
  137. id = finalId;
  138. }
  139. var entity = entityCollection.getOrCreateEntity(id);
  140. var properties = geoJson.properties;
  141. if (defined(properties)) {
  142. entity.addProperty('properties');
  143. entity.properties = properties;
  144. var nameProperty;
  145. //Check for the simplestyle specified name first.
  146. var name = properties.title;
  147. if (defined(name)) {
  148. entity.name = name;
  149. nameProperty = 'title';
  150. } else {
  151. //Else, find the name by selecting an appropriate property.
  152. //The name will be obtained based on this order:
  153. //1) The first case-insensitive property with the name 'title',
  154. //2) The first case-insensitive property with the name 'name',
  155. //3) The first property containing the word 'title'.
  156. //4) The first property containing the word 'name',
  157. var namePropertyPrecedence = Number.MAX_VALUE;
  158. for ( var key in properties) {
  159. if (properties.hasOwnProperty(key) && properties[key]) {
  160. var lowerKey = key.toLowerCase();
  161. if (namePropertyPrecedence > 1 && lowerKey === 'title') {
  162. namePropertyPrecedence = 1;
  163. nameProperty = key;
  164. break;
  165. } else if (namePropertyPrecedence > 2 && lowerKey === 'name') {
  166. namePropertyPrecedence = 2;
  167. nameProperty = key;
  168. } else if (namePropertyPrecedence > 3 && /title/i.test(key)) {
  169. namePropertyPrecedence = 3;
  170. nameProperty = key;
  171. } else if (namePropertyPrecedence > 4 && /name/i.test(key)) {
  172. namePropertyPrecedence = 4;
  173. nameProperty = key;
  174. }
  175. }
  176. }
  177. if (defined(nameProperty)) {
  178. entity.name = properties[nameProperty];
  179. }
  180. }
  181. var description = properties.description;
  182. if (description !== null) {
  183. entity.description = !defined(description) ? describe(properties, nameProperty) : new ConstantProperty(description);
  184. }
  185. }
  186. return entity;
  187. }
  188. function coordinatesArrayToCartesianArray(coordinates, crsFunction) {
  189. var positions = new Array(coordinates.length);
  190. for (var i = 0; i < coordinates.length; i++) {
  191. positions[i] = crsFunction(coordinates[i]);
  192. }
  193. return positions;
  194. }
  195. // GeoJSON processing functions
  196. function processFeature(dataSource, feature, notUsed, crsFunction, options) {
  197. if (feature.geometry === null) {
  198. //Null geometry is allowed, so just create an empty entity instance for it.
  199. createObject(feature, dataSource._entityCollection, options.describe);
  200. return;
  201. }
  202. if (!defined(feature.geometry)) {
  203. throw new RuntimeError('feature.geometry is required.');
  204. }
  205. var geometryType = feature.geometry.type;
  206. var geometryHandler = geometryTypes[geometryType];
  207. if (!defined(geometryHandler)) {
  208. throw new RuntimeError('Unknown geometry type: ' + geometryType);
  209. }
  210. geometryHandler(dataSource, feature, feature.geometry, crsFunction, options);
  211. }
  212. function processFeatureCollection(dataSource, featureCollection, notUsed, crsFunction, options) {
  213. var features = featureCollection.features;
  214. for (var i = 0, len = features.length; i < len; i++) {
  215. processFeature(dataSource, features[i], undefined, crsFunction, options);
  216. }
  217. }
  218. function processGeometryCollection(dataSource, geoJson, geometryCollection, crsFunction, options) {
  219. var geometries = geometryCollection.geometries;
  220. for (var i = 0, len = geometries.length; i < len; i++) {
  221. var geometry = geometries[i];
  222. var geometryType = geometry.type;
  223. var geometryHandler = geometryTypes[geometryType];
  224. if (!defined(geometryHandler)) {
  225. throw new RuntimeError('Unknown geometry type: ' + geometryType);
  226. }
  227. geometryHandler(dataSource, geoJson, geometry, crsFunction, options);
  228. }
  229. }
  230. function createPoint(dataSource, geoJson, crsFunction, coordinates, options) {
  231. var symbol = options.markerSymbol;
  232. var color = options.markerColor;
  233. var size = options.markerSize;
  234. var properties = geoJson.properties;
  235. if (defined(properties)) {
  236. var cssColor = properties['marker-color'];
  237. if (defined(cssColor)) {
  238. color = Color.fromCssColorString(cssColor);
  239. }
  240. size = defaultValue(sizes[properties['marker-size']], size);
  241. var markerSymbol = properties['marker-symbol'];
  242. if (defined(markerSymbol)) {
  243. symbol = markerSymbol;
  244. }
  245. }
  246. var canvasOrPromise;
  247. if (defined(symbol)) {
  248. if (symbol.length === 1) {
  249. canvasOrPromise = dataSource._pinBuilder.fromText(symbol.toUpperCase(), color, size);
  250. } else {
  251. canvasOrPromise = dataSource._pinBuilder.fromMakiIconId(symbol, color, size);
  252. }
  253. } else {
  254. canvasOrPromise = dataSource._pinBuilder.fromColor(color, size);
  255. }
  256. var billboard = new BillboardGraphics();
  257. billboard.verticalOrigin = new ConstantProperty(VerticalOrigin.BOTTOM);
  258. // Clamp to ground if there isn't a height specified
  259. if (coordinates.length === 2 && options.clampToGround) {
  260. billboard.heightReference = HeightReference.CLAMP_TO_GROUND;
  261. }
  262. var entity = createObject(geoJson, dataSource._entityCollection, options.describe);
  263. entity.billboard = billboard;
  264. entity.position = new ConstantPositionProperty(crsFunction(coordinates));
  265. var promise = when(canvasOrPromise).then(function(image) {
  266. billboard.image = new ConstantProperty(image);
  267. }).otherwise(function() {
  268. billboard.image = new ConstantProperty(dataSource._pinBuilder.fromColor(color, size));
  269. });
  270. dataSource._promises.push(promise);
  271. }
  272. function processPoint(dataSource, geoJson, geometry, crsFunction, options) {
  273. createPoint(dataSource, geoJson, crsFunction, geometry.coordinates, options);
  274. }
  275. function processMultiPoint(dataSource, geoJson, geometry, crsFunction, options) {
  276. var coordinates = geometry.coordinates;
  277. for (var i = 0; i < coordinates.length; i++) {
  278. createPoint(dataSource, geoJson, crsFunction, coordinates[i], options);
  279. }
  280. }
  281. function createLineString(dataSource, geoJson, crsFunction, coordinates, options) {
  282. var material = options.strokeMaterialProperty;
  283. var widthProperty = options.strokeWidthProperty;
  284. var properties = geoJson.properties;
  285. if (defined(properties)) {
  286. var width = properties['stroke-width'];
  287. if (defined(width)) {
  288. widthProperty = new ConstantProperty(width);
  289. }
  290. var color;
  291. var stroke = properties.stroke;
  292. if (defined(stroke)) {
  293. color = Color.fromCssColorString(stroke);
  294. }
  295. var opacity = properties['stroke-opacity'];
  296. if (defined(opacity) && opacity !== 1.0) {
  297. if (!defined(color)) {
  298. color = material.color.clone();
  299. }
  300. color.alpha = opacity;
  301. }
  302. if (defined(color)) {
  303. material = new ColorMaterialProperty(color);
  304. }
  305. }
  306. var entity = createObject(geoJson, dataSource._entityCollection, options.describe);
  307. var graphics;
  308. if (options.clampToGround) {
  309. graphics = new CorridorGraphics();
  310. entity.corridor = graphics;
  311. } else {
  312. graphics = new PolylineGraphics();
  313. entity.polyline = graphics;
  314. }
  315. graphics.material = material;
  316. graphics.width = widthProperty;
  317. graphics.positions = new ConstantProperty(coordinatesArrayToCartesianArray(coordinates, crsFunction));
  318. }
  319. function processLineString(dataSource, geoJson, geometry, crsFunction, options) {
  320. createLineString(dataSource, geoJson, crsFunction, geometry.coordinates, options);
  321. }
  322. function processMultiLineString(dataSource, geoJson, geometry, crsFunction, options) {
  323. var lineStrings = geometry.coordinates;
  324. for (var i = 0; i < lineStrings.length; i++) {
  325. createLineString(dataSource, geoJson, crsFunction, lineStrings[i], options);
  326. }
  327. }
  328. function createPolygon(dataSource, geoJson, crsFunction, coordinates, options) {
  329. if (coordinates.length === 0 || coordinates[0].length === 0) {
  330. return;
  331. }
  332. var outlineColorProperty = options.strokeMaterialProperty.color;
  333. var material = options.fillMaterialProperty;
  334. var widthProperty = options.strokeWidthProperty;
  335. var properties = geoJson.properties;
  336. if (defined(properties)) {
  337. var width = properties['stroke-width'];
  338. if (defined(width)) {
  339. widthProperty = new ConstantProperty(width);
  340. }
  341. var color;
  342. var stroke = properties.stroke;
  343. if (defined(stroke)) {
  344. color = Color.fromCssColorString(stroke);
  345. }
  346. var opacity = properties['stroke-opacity'];
  347. if (defined(opacity) && opacity !== 1.0) {
  348. if (!defined(color)) {
  349. color = options.strokeMaterialProperty.color.clone();
  350. }
  351. color.alpha = opacity;
  352. }
  353. if (defined(color)) {
  354. outlineColorProperty = new ConstantProperty(color);
  355. }
  356. var fillColor;
  357. var fill = properties.fill;
  358. if (defined(fill)) {
  359. fillColor = Color.fromCssColorString(fill);
  360. fillColor.alpha = material.color.alpha;
  361. }
  362. opacity = properties['fill-opacity'];
  363. if (defined(opacity) && opacity !== material.color.alpha) {
  364. if (!defined(fillColor)) {
  365. fillColor = material.color.clone();
  366. }
  367. fillColor.alpha = opacity;
  368. }
  369. if (defined(fillColor)) {
  370. material = new ColorMaterialProperty(fillColor);
  371. }
  372. }
  373. var polygon = new PolygonGraphics();
  374. polygon.outline = new ConstantProperty(true);
  375. polygon.outlineColor = outlineColorProperty;
  376. polygon.outlineWidth = widthProperty;
  377. polygon.material = material;
  378. var holes = [];
  379. for (var i = 1, len = coordinates.length; i < len; i++) {
  380. holes.push(new PolygonHierarchy(coordinatesArrayToCartesianArray(coordinates[i], crsFunction)));
  381. }
  382. var positions = coordinates[0];
  383. polygon.hierarchy = new ConstantProperty(new PolygonHierarchy(coordinatesArrayToCartesianArray(positions, crsFunction), holes));
  384. if (positions[0].length > 2) {
  385. polygon.perPositionHeight = new ConstantProperty(true);
  386. } else if (!options.clampToGround) {
  387. polygon.height = 0;
  388. }
  389. var entity = createObject(geoJson, dataSource._entityCollection, options.describe);
  390. entity.polygon = polygon;
  391. }
  392. function processPolygon(dataSource, geoJson, geometry, crsFunction, options) {
  393. createPolygon(dataSource, geoJson, crsFunction, geometry.coordinates, options);
  394. }
  395. function processMultiPolygon(dataSource, geoJson, geometry, crsFunction, options) {
  396. var polygons = geometry.coordinates;
  397. for (var i = 0; i < polygons.length; i++) {
  398. createPolygon(dataSource, geoJson, crsFunction, polygons[i], options);
  399. }
  400. }
  401. function processTopology(dataSource, geoJson, geometry, crsFunction, options) {
  402. for ( var property in geometry.objects) {
  403. if (geometry.objects.hasOwnProperty(property)) {
  404. var feature = topojson.feature(geometry, geometry.objects[property]);
  405. var typeHandler = geoJsonObjectTypes[feature.type];
  406. typeHandler(dataSource, feature, feature, crsFunction, options);
  407. }
  408. }
  409. }
  410. var geoJsonObjectTypes = {
  411. Feature : processFeature,
  412. FeatureCollection : processFeatureCollection,
  413. GeometryCollection : processGeometryCollection,
  414. LineString : processLineString,
  415. MultiLineString : processMultiLineString,
  416. MultiPoint : processMultiPoint,
  417. MultiPolygon : processMultiPolygon,
  418. Point : processPoint,
  419. Polygon : processPolygon,
  420. Topology : processTopology
  421. };
  422. var geometryTypes = {
  423. GeometryCollection : processGeometryCollection,
  424. LineString : processLineString,
  425. MultiLineString : processMultiLineString,
  426. MultiPoint : processMultiPoint,
  427. MultiPolygon : processMultiPolygon,
  428. Point : processPoint,
  429. Polygon : processPolygon,
  430. Topology : processTopology
  431. };
  432. /**
  433. * A {@link DataSource} which processes both
  434. * {@link http://www.geojson.org/|GeoJSON} and {@link https://github.com/mbostock/topojson|TopoJSON} data.
  435. * {@link https://github.com/mapbox/simplestyle-spec|simplestyle-spec} properties will also be used if they
  436. * are present.
  437. *
  438. * @alias GeoJsonDataSource
  439. * @constructor
  440. *
  441. * @param {String} [name] The name of this data source. If undefined, a name will be taken from
  442. * the name of the GeoJSON file.
  443. *
  444. * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=GeoJSON%20and%20TopoJSON.html|Cesium Sandcastle GeoJSON and TopoJSON Demo}
  445. * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=GeoJSON%20simplestyle.html|Cesium Sandcastle GeoJSON simplestyle Demo}
  446. *
  447. * @example
  448. * var viewer = new Cesium.Viewer('cesiumContainer');
  449. * viewer.dataSources.add(Cesium.GeoJsonDataSource.load('../../SampleData/ne_10m_us_states.topojson', {
  450. * stroke: Cesium.Color.HOTPINK,
  451. * fill: Cesium.Color.PINK,
  452. * strokeWidth: 3,
  453. * markerSymbol: '?'
  454. * }));
  455. */
  456. function GeoJsonDataSource(name) {
  457. this._name = name;
  458. this._changed = new Event();
  459. this._error = new Event();
  460. this._isLoading = false;
  461. this._loading = new Event();
  462. this._entityCollection = new EntityCollection(this);
  463. this._promises = [];
  464. this._pinBuilder = new PinBuilder();
  465. this._entityCluster = new EntityCluster();
  466. }
  467. /**
  468. * Creates a Promise to a new instance loaded with the provided GeoJSON or TopoJSON data.
  469. *
  470. * @param {String|Object} data A url, GeoJSON object, or TopoJSON object to be loaded.
  471. * @param {Object} [options] An object with the following properties:
  472. * @param {String} [options.sourceUri] Overrides the url to use for resolving relative links.
  473. * @param {Number} [options.markerSize=GeoJsonDataSource.markerSize] The default size of the map pin created for each point, in pixels.
  474. * @param {String} [options.markerSymbol=GeoJsonDataSource.markerSymbol] The default symbol of the map pin created for each point.
  475. * @param {Color} [options.markerColor=GeoJsonDataSource.markerColor] The default color of the map pin created for each point.
  476. * @param {Color} [options.stroke=GeoJsonDataSource.stroke] The default color of polylines and polygon outlines.
  477. * @param {Number} [options.strokeWidth=GeoJsonDataSource.strokeWidth] The default width of polylines and polygon outlines.
  478. * @param {Color} [options.fill=GeoJsonDataSource.fill] The default color for polygon interiors.
  479. * @param {Boolean} [options.clampToGround=GeoJsonDataSource.clampToGround] true if we want the geometry features (polygons or linestrings) clamped to the ground. If true, lines will use corridors so use Entity.corridor instead of Entity.polyline.
  480. *
  481. * @returns {Promise.<GeoJsonDataSource>} A promise that will resolve when the data is loaded.
  482. */
  483. GeoJsonDataSource.load = function(data, options) {
  484. return new GeoJsonDataSource().load(data, options);
  485. };
  486. defineProperties(GeoJsonDataSource, {
  487. /**
  488. * Gets or sets the default size of the map pin created for each point, in pixels.
  489. * @memberof GeoJsonDataSource
  490. * @type {Number}
  491. * @default 48
  492. */
  493. markerSize : {
  494. get : function() {
  495. return defaultMarkerSize;
  496. },
  497. set : function(value) {
  498. defaultMarkerSize = value;
  499. }
  500. },
  501. /**
  502. * Gets or sets the default symbol of the map pin created for each point.
  503. * This can be any valid {@link http://mapbox.com/maki/|Maki} identifier, any single character,
  504. * or blank if no symbol is to be used.
  505. * @memberof GeoJsonDataSource
  506. * @type {String}
  507. */
  508. markerSymbol : {
  509. get : function() {
  510. return defaultMarkerSymbol;
  511. },
  512. set : function(value) {
  513. defaultMarkerSymbol = value;
  514. }
  515. },
  516. /**
  517. * Gets or sets the default color of the map pin created for each point.
  518. * @memberof GeoJsonDataSource
  519. * @type {Color}
  520. * @default Color.ROYALBLUE
  521. */
  522. markerColor : {
  523. get : function() {
  524. return defaultMarkerColor;
  525. },
  526. set : function(value) {
  527. defaultMarkerColor = value;
  528. }
  529. },
  530. /**
  531. * Gets or sets the default color of polylines and polygon outlines.
  532. * @memberof GeoJsonDataSource
  533. * @type {Color}
  534. * @default Color.BLACK
  535. */
  536. stroke : {
  537. get : function() {
  538. return defaultStroke;
  539. },
  540. set : function(value) {
  541. defaultStroke = value;
  542. defaultStrokeMaterialProperty.color.setValue(value);
  543. }
  544. },
  545. /**
  546. * Gets or sets the default width of polylines and polygon outlines.
  547. * @memberof GeoJsonDataSource
  548. * @type {Number}
  549. * @default 2.0
  550. */
  551. strokeWidth : {
  552. get : function() {
  553. return defaultStrokeWidth;
  554. },
  555. set : function(value) {
  556. defaultStrokeWidth = value;
  557. defaultStrokeWidthProperty.setValue(value);
  558. }
  559. },
  560. /**
  561. * Gets or sets default color for polygon interiors.
  562. * @memberof GeoJsonDataSource
  563. * @type {Color}
  564. * @default Color.YELLOW
  565. */
  566. fill : {
  567. get : function() {
  568. return defaultFill;
  569. },
  570. set : function(value) {
  571. defaultFill = value;
  572. defaultFillMaterialProperty = new ColorMaterialProperty(defaultFill);
  573. }
  574. },
  575. /**
  576. * Gets or sets default of whether to clamp to the ground.
  577. * @memberof GeoJsonDataSource
  578. * @type {Boolean}
  579. * @default false
  580. */
  581. clampToGround : {
  582. get : function() {
  583. return defaultClampToGround;
  584. },
  585. set : function(value) {
  586. defaultClampToGround = value;
  587. }
  588. },
  589. /**
  590. * Gets an object that maps the name of a crs to a callback function which takes a GeoJSON coordinate
  591. * and transforms it into a WGS84 Earth-fixed Cartesian. Older versions of GeoJSON which
  592. * supported the EPSG type can be added to this list as well, by specifying the complete EPSG name,
  593. * for example 'EPSG:4326'.
  594. * @memberof GeoJsonDataSource
  595. * @type {Object}
  596. */
  597. crsNames : {
  598. get : function() {
  599. return crsNames;
  600. }
  601. },
  602. /**
  603. * Gets an object that maps the href property of a crs link to a callback function
  604. * which takes the crs properties object and returns a Promise that resolves
  605. * to a function that takes a GeoJSON coordinate and transforms it into a WGS84 Earth-fixed Cartesian.
  606. * Items in this object take precedence over those defined in <code>crsLinkHrefs</code>, assuming
  607. * the link has a type specified.
  608. * @memberof GeoJsonDataSource
  609. * @type {Object}
  610. */
  611. crsLinkHrefs : {
  612. get : function() {
  613. return crsLinkHrefs;
  614. }
  615. },
  616. /**
  617. * Gets an object that maps the type property of a crs link to a callback function
  618. * which takes the crs properties object and returns a Promise that resolves
  619. * to a function that takes a GeoJSON coordinate and transforms it into a WGS84 Earth-fixed Cartesian.
  620. * Items in <code>crsLinkHrefs</code> take precedence over this object.
  621. * @memberof GeoJsonDataSource
  622. * @type {Object}
  623. */
  624. crsLinkTypes : {
  625. get : function() {
  626. return crsLinkTypes;
  627. }
  628. }
  629. });
  630. defineProperties(GeoJsonDataSource.prototype, {
  631. /**
  632. * Gets a human-readable name for this instance.
  633. * @memberof GeoJsonDataSource.prototype
  634. * @type {String}
  635. */
  636. name : {
  637. get : function() {
  638. return this._name;
  639. }
  640. },
  641. /**
  642. * This DataSource only defines static data, therefore this property is always undefined.
  643. * @memberof GeoJsonDataSource.prototype
  644. * @type {DataSourceClock}
  645. */
  646. clock : {
  647. value : undefined,
  648. writable : false
  649. },
  650. /**
  651. * Gets the collection of {@link Entity} instances.
  652. * @memberof GeoJsonDataSource.prototype
  653. * @type {EntityCollection}
  654. */
  655. entities : {
  656. get : function() {
  657. return this._entityCollection;
  658. }
  659. },
  660. /**
  661. * Gets a value indicating if the data source is currently loading data.
  662. * @memberof GeoJsonDataSource.prototype
  663. * @type {Boolean}
  664. */
  665. isLoading : {
  666. get : function() {
  667. return this._isLoading;
  668. }
  669. },
  670. /**
  671. * Gets an event that will be raised when the underlying data changes.
  672. * @memberof GeoJsonDataSource.prototype
  673. * @type {Event}
  674. */
  675. changedEvent : {
  676. get : function() {
  677. return this._changed;
  678. }
  679. },
  680. /**
  681. * Gets an event that will be raised if an error is encountered during processing.
  682. * @memberof GeoJsonDataSource.prototype
  683. * @type {Event}
  684. */
  685. errorEvent : {
  686. get : function() {
  687. return this._error;
  688. }
  689. },
  690. /**
  691. * Gets an event that will be raised when the data source either starts or stops loading.
  692. * @memberof GeoJsonDataSource.prototype
  693. * @type {Event}
  694. */
  695. loadingEvent : {
  696. get : function() {
  697. return this._loading;
  698. }
  699. },
  700. /**
  701. * Gets whether or not this data source should be displayed.
  702. * @memberof GeoJsonDataSource.prototype
  703. * @type {Boolean}
  704. */
  705. show : {
  706. get : function() {
  707. return this._entityCollection.show;
  708. },
  709. set : function(value) {
  710. this._entityCollection.show = value;
  711. }
  712. },
  713. /**
  714. * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
  715. *
  716. * @memberof GeoJsonDataSource.prototype
  717. * @type {EntityCluster}
  718. */
  719. clustering : {
  720. get : function() {
  721. return this._entityCluster;
  722. },
  723. set : function(value) {
  724. //>>includeStart('debug', pragmas.debug);
  725. if (!defined(value)) {
  726. throw new DeveloperError('value must be defined.');
  727. }
  728. //>>includeEnd('debug');
  729. this._entityCluster = value;
  730. }
  731. }
  732. });
  733. /**
  734. * Asynchronously loads the provided GeoJSON or TopoJSON data, replacing any existing data.
  735. *
  736. * @param {String|Object} data A url, GeoJSON object, or TopoJSON object to be loaded.
  737. * @param {Object} [options] An object with the following properties:
  738. * @param {String} [options.sourceUri] Overrides the url to use for resolving relative links.
  739. * @param {GeoJsonDataSource~describe} [options.describe=GeoJsonDataSource.defaultDescribeProperty] A function which returns a Property object (or just a string),
  740. * which converts the properties into an html description.
  741. * @param {Number} [options.markerSize=GeoJsonDataSource.markerSize] The default size of the map pin created for each point, in pixels.
  742. * @param {String} [options.markerSymbol=GeoJsonDataSource.markerSymbol] The default symbol of the map pin created for each point.
  743. * @param {Color} [options.markerColor=GeoJsonDataSource.markerColor] The default color of the map pin created for each point.
  744. * @param {Color} [options.stroke=GeoJsonDataSource.stroke] The default color of polylines and polygon outlines.
  745. * @param {Number} [options.strokeWidth=GeoJsonDataSource.strokeWidth] The default width of polylines and polygon outlines.
  746. * @param {Color} [options.fill=GeoJsonDataSource.fill] The default color for polygon interiors.
  747. * @param {Boolean} [options.clampToGround=GeoJsonDataSource.clampToGround] true if we want the features clamped to the ground.
  748. *
  749. * @returns {Promise.<GeoJsonDataSource>} a promise that will resolve when the GeoJSON is loaded.
  750. */
  751. GeoJsonDataSource.prototype.load = function(data, options) {
  752. //>>includeStart('debug', pragmas.debug);
  753. if (!defined(data)) {
  754. throw new DeveloperError('data is required.');
  755. }
  756. //>>includeEnd('debug');
  757. DataSource.setLoading(this, true);
  758. var promise = data;
  759. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  760. var sourceUri = options.sourceUri;
  761. if (typeof data === 'string') {
  762. if (!defined(sourceUri)) {
  763. sourceUri = data;
  764. }
  765. promise = loadJson(data);
  766. }
  767. options = {
  768. describe: defaultValue(options.describe, defaultDescribeProperty),
  769. markerSize : defaultValue(options.markerSize, defaultMarkerSize),
  770. markerSymbol : defaultValue(options.markerSymbol, defaultMarkerSymbol),
  771. markerColor : defaultValue(options.markerColor, defaultMarkerColor),
  772. strokeWidthProperty : new ConstantProperty(defaultValue(options.strokeWidth, defaultStrokeWidth)),
  773. strokeMaterialProperty : new ColorMaterialProperty(defaultValue(options.stroke, defaultStroke)),
  774. fillMaterialProperty : new ColorMaterialProperty(defaultValue(options.fill, defaultFill)),
  775. clampToGround : defaultValue(options.clampToGround, defaultClampToGround)
  776. };
  777. var that = this;
  778. return when(promise, function(geoJson) {
  779. return load(that, geoJson, options, sourceUri);
  780. }).otherwise(function(error) {
  781. DataSource.setLoading(that, false);
  782. that._error.raiseEvent(that, error);
  783. console.log(error);
  784. return when.reject(error);
  785. });
  786. };
  787. function load(that, geoJson, options, sourceUri) {
  788. var name;
  789. if (defined(sourceUri)) {
  790. name = getFilenameFromUri(sourceUri);
  791. }
  792. if (defined(name) && that._name !== name) {
  793. that._name = name;
  794. that._changed.raiseEvent(that);
  795. }
  796. var typeHandler = geoJsonObjectTypes[geoJson.type];
  797. if (!defined(typeHandler)) {
  798. throw new RuntimeError('Unsupported GeoJSON object type: ' + geoJson.type);
  799. }
  800. //Check for a Coordinate Reference System.
  801. var crs = geoJson.crs;
  802. var crsFunction = crs !== null ? defaultCrsFunction : null;
  803. if (defined(crs)) {
  804. if (!defined(crs.properties)) {
  805. throw new RuntimeError('crs.properties is undefined.');
  806. }
  807. var properties = crs.properties;
  808. if (crs.type === 'name') {
  809. crsFunction = crsNames[properties.name];
  810. if (!defined(crsFunction)) {
  811. throw new RuntimeError('Unknown crs name: ' + properties.name);
  812. }
  813. } else if (crs.type === 'link') {
  814. var handler = crsLinkHrefs[properties.href];
  815. if (!defined(handler)) {
  816. handler = crsLinkTypes[properties.type];
  817. }
  818. if (!defined(handler)) {
  819. throw new RuntimeError('Unable to resolve crs link: ' + JSON.stringify(properties));
  820. }
  821. crsFunction = handler(properties);
  822. } else if (crs.type === 'EPSG') {
  823. crsFunction = crsNames['EPSG:' + properties.code];
  824. if (!defined(crsFunction)) {
  825. throw new RuntimeError('Unknown crs EPSG code: ' + properties.code);
  826. }
  827. } else {
  828. throw new RuntimeError('Unknown crs type: ' + crs.type);
  829. }
  830. }
  831. return when(crsFunction, function(crsFunction) {
  832. that._entityCollection.removeAll();
  833. // null is a valid value for the crs, but means the entire load process becomes a no-op
  834. // because we can't assume anything about the coordinates.
  835. if (crsFunction !== null) {
  836. typeHandler(that, geoJson, geoJson, crsFunction, options);
  837. }
  838. return when.all(that._promises, function() {
  839. that._promises.length = 0;
  840. DataSource.setLoading(that, false);
  841. return that;
  842. });
  843. });
  844. }
  845. /**
  846. * This callback is displayed as part of the GeoJsonDataSource class.
  847. * @callback GeoJsonDataSource~describe
  848. * @param {Object} properties The properties of the feature.
  849. * @param {String} nameProperty The property key that Cesium estimates to have the name of the feature.
  850. */
  851. return GeoJsonDataSource;
  852. });