智慧水务管理系统 - 精河县供水工程综合管理平台

GeoJsonPrimitive.js 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. // @ts-check
  2. /** @import {GeoJson, GeoJsonFeature, GeoJsonGeometry, GeoJsonPosition} from "../Core/globalTypes.js"; */
  3. /** @import FrameState from "./FrameState.js"; */
  4. import Cartesian2 from "../Core/Cartesian2.js";
  5. import Cartesian3 from "../Core/Cartesian3.js";
  6. import Check from "../Core/Check.js";
  7. import defined from "../Core/defined.js";
  8. import destroyObject from "../Core/destroyObject.js";
  9. import DeveloperError from "../Core/DeveloperError.js";
  10. import Ellipsoid from "../Core/Ellipsoid.js";
  11. import Frozen from "../Core/Frozen.js";
  12. import PolygonPipeline from "../Core/PolygonPipeline.js";
  13. import Resource from "../Core/Resource.js";
  14. import RuntimeError from "../Core/RuntimeError.js";
  15. import BufferPoint from "./BufferPoint.js";
  16. import BufferPointCollection from "./BufferPointCollection.js";
  17. import BufferPolygon from "./BufferPolygon.js";
  18. import BufferPolygonCollection from "./BufferPolygonCollection.js";
  19. import BufferPolyline from "./BufferPolyline.js";
  20. import BufferPolylineCollection from "./BufferPolylineCollection.js";
  21. /**
  22. * @typedef {object} GeoJsonPrimitiveConstructorOptions
  23. * @property {object} [geoJson]
  24. * @property {Resource|string} [url]
  25. * @property {Ellipsoid} [ellipsoid=Ellipsoid.default]
  26. * @property {boolean} [allowPicking=true]
  27. * @property {boolean} [show=true]
  28. * @property {function(number, object, Record<string, unknown>):object} [pickObjectFactory]
  29. */
  30. /**
  31. * Lightweight GeoJSON loader that converts features directly into
  32. * {@link BufferPointCollection}, {@link BufferPolylineCollection}, and
  33. * {@link BufferPolygonCollection}.
  34. *
  35. * Unlike {@link GeoJsonDataSource}, this path does not create entities.
  36. * Instead, it exposes high-throughput buffer primitive collections that can be
  37. * added directly to {@link Scene#primitives}.
  38. *
  39. * @example
  40. * const loader = await Cesium.GeoJsonPrimitive.fromUrl("./data.geojson");
  41. * viewer.scene.primitives.add(loader);
  42. *
  43. * loader.points; // BufferPointCollection | undefined
  44. * loader.polylines; // BufferPolylineCollection | undefined
  45. * loader.polygons; // BufferPolygonCollection | undefined
  46. * loader.ids; // source feature IDs
  47. * loader.properties; // source feature properties
  48. * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
  49. */
  50. class GeoJsonPrimitive {
  51. /**
  52. * @param {GeoJsonPrimitiveConstructorOptions} [options]
  53. */
  54. constructor(options) {
  55. options = options ?? Frozen.EMPTY_OBJECT;
  56. //>>includeStart('debug', pragmas.debug);
  57. if (!defined(options.geoJson)) {
  58. throw new DeveloperError("options.geoJson is required.");
  59. }
  60. //>>includeEnd('debug');
  61. const parseResult = parseGeoJson(/** @type {GeoJson} */ (options.geoJson));
  62. const allowPicking = options.allowPicking ?? true;
  63. const ellipsoid = options.ellipsoid ?? Ellipsoid.default;
  64. let packedPositionsScratch = new Float64Array(0);
  65. /** @param {number} requiredLength */
  66. function getPackedPositionScratch(requiredLength) {
  67. if (packedPositionsScratch.length < requiredLength) {
  68. packedPositionsScratch = new Float64Array(requiredLength);
  69. }
  70. return packedPositionsScratch;
  71. }
  72. this.show = options.show ?? true;
  73. this._url = options.url;
  74. this._ids = parseResult.ids;
  75. this._properties = parseResult.properties;
  76. this._featureCount = parseResult.ids.length;
  77. this._pickObjectFactory = options.pickObjectFactory;
  78. this._points = undefined;
  79. this._polylines = undefined;
  80. this._polygons = undefined;
  81. if (parseResult.pointCount > 0) {
  82. /** @type {Record<string, unknown>} */
  83. const pointOptions = {
  84. primitiveCountMax: parseResult.pointCount,
  85. allowPicking: allowPicking,
  86. };
  87. this._points = new BufferPointCollection(pointOptions);
  88. }
  89. if (parseResult.polylineCount > 0) {
  90. /** @type {Record<string, unknown>} */
  91. const polylineOptions = {
  92. primitiveCountMax: parseResult.polylineCount,
  93. vertexCountMax: parseResult.polylineVertexCount,
  94. allowPicking: allowPicking,
  95. };
  96. this._polylines = new BufferPolylineCollection(polylineOptions);
  97. }
  98. if (parseResult.polygonCount > 0) {
  99. /** @type {Record<string, unknown>} */
  100. const polygonOptions = {
  101. primitiveCountMax: parseResult.polygonCount,
  102. vertexCountMax: parseResult.polygonVertexCount,
  103. holeCountMax: parseResult.polygonHoleCount,
  104. triangleCountMax: parseResult.polygonTriangleCount,
  105. allowPicking: allowPicking,
  106. };
  107. this._polygons = new BufferPolygonCollection(polygonOptions);
  108. }
  109. const scratch = new Cartesian3();
  110. let pointIndex = 0;
  111. let polylineIndex = 0;
  112. let polygonIndex = 0;
  113. for (let i = 0; i < parseResult.features.length; i++) {
  114. const feature = parseResult.features[i];
  115. const featureId = feature.featureId;
  116. const sourceProperties = this._properties[featureId];
  117. for (let j = 0; j < feature.points.length; j++) {
  118. const idx = pointIndex++;
  119. this._points.add({
  120. featureId: featureId,
  121. position: toCartesian(feature.points[j], ellipsoid, scratch),
  122. pickObject: allowPicking
  123. ? createPickObject(
  124. this,
  125. idx,
  126. this._points,
  127. BufferPoint,
  128. sourceProperties,
  129. )
  130. : undefined,
  131. });
  132. }
  133. for (let j = 0; j < feature.polylines.length; j++) {
  134. const idx = polylineIndex++;
  135. this._polylines.add({
  136. featureId: featureId,
  137. positions: packPositionsToScratch(
  138. feature.polylines[j],
  139. ellipsoid,
  140. getPackedPositionScratch,
  141. ),
  142. pickObject: allowPicking
  143. ? createPickObject(
  144. this,
  145. idx,
  146. this._polylines,
  147. BufferPolyline,
  148. sourceProperties,
  149. )
  150. : undefined,
  151. });
  152. }
  153. for (let j = 0; j < feature.polygons.length; j++) {
  154. const polygon = feature.polygons[j];
  155. const idx = polygonIndex++;
  156. this._polygons.add({
  157. featureId: featureId,
  158. positions: packPositionsToScratch(
  159. polygon.positions,
  160. ellipsoid,
  161. getPackedPositionScratch,
  162. ),
  163. holes: polygon.holes,
  164. triangles: polygon.triangles,
  165. pickObject: allowPicking
  166. ? createPickObject(
  167. this,
  168. idx,
  169. this._polygons,
  170. BufferPolygon,
  171. sourceProperties,
  172. )
  173. : undefined,
  174. });
  175. }
  176. }
  177. }
  178. /**
  179. * Loader source URL when created via {@link GeoJsonPrimitive.fromUrl}.
  180. *
  181. * @type {string|undefined}
  182. * @readonly
  183. */
  184. get url() {
  185. const url = this._url;
  186. if (!defined(url)) {
  187. return undefined;
  188. }
  189. return url instanceof Resource ? url.getUrlComponent(true) : url;
  190. }
  191. /**
  192. * Feature count represented by the loaded collections.
  193. *
  194. * @type {number}
  195. * @readonly
  196. */
  197. get featureCount() {
  198. return this._featureCount;
  199. }
  200. /**
  201. * Lookup table from integer ID generated by GeoJsonPrimitive, to integer or string Feature ID from GeoJSON source.
  202. *
  203. * @type {Array<string|number|undefined>}
  204. * @readonly
  205. */
  206. get ids() {
  207. return this._ids;
  208. }
  209. /**
  210. * Source GeoJSON properties, indexed by generated integer ID.
  211. *
  212. * @type {Array<Record<string, unknown>>}
  213. * @readonly
  214. */
  215. get properties() {
  216. return this._properties;
  217. }
  218. /**
  219. * Buffer point collection for point geometries.
  220. *
  221. * @type {BufferPointCollection|undefined}
  222. * @readonly
  223. */
  224. get points() {
  225. return this._points;
  226. }
  227. /**
  228. * Buffer polyline collection for linestring geometries.
  229. *
  230. * @type {BufferPolylineCollection|undefined}
  231. * @readonly
  232. */
  233. get polylines() {
  234. return this._polylines;
  235. }
  236. /**
  237. * Buffer polygon collection for polygon geometries.
  238. *
  239. * @type {BufferPolygonCollection|undefined}
  240. * @readonly
  241. */
  242. get polygons() {
  243. return this._polygons;
  244. }
  245. /**
  246. * Loads GeoJSON from a URL or {@link Resource}.
  247. *
  248. * @param {Resource|string} url
  249. * @param {GeoJsonPrimitiveConstructorOptions} [options]
  250. * @returns {Promise<GeoJsonPrimitive>}
  251. */
  252. static async fromUrl(url, options) {
  253. //>>includeStart('debug', pragmas.debug);
  254. if (!defined(url)) {
  255. throw new DeveloperError("url is required.");
  256. }
  257. //>>includeEnd('debug');
  258. const resource = Resource.createIfNeeded(url);
  259. const geoJson = await resource.fetchJson();
  260. if (!defined(geoJson)) {
  261. throw new RuntimeError(
  262. `Failed to load GeoJSON from ${resource.getUrlComponent(true)}.`,
  263. );
  264. }
  265. return GeoJsonPrimitive.fromGeoJson(geoJson, {
  266. ...options,
  267. url: resource,
  268. });
  269. }
  270. /**
  271. * Creates a loader directly from a parsed GeoJSON object.
  272. *
  273. * @param {object} geoJson
  274. * @param {GeoJsonPrimitiveConstructorOptions} [options]
  275. * @returns {GeoJsonPrimitive}
  276. */
  277. static fromGeoJson(geoJson, options) {
  278. return new GeoJsonPrimitive({
  279. ...options,
  280. geoJson: geoJson,
  281. });
  282. }
  283. /**
  284. * @param {number} featureId
  285. */
  286. getId(featureId) {
  287. //>>includeStart('debug', pragmas.debug);
  288. Check.typeOf.number.greaterThanOrEquals("featureId", featureId, 0);
  289. Check.typeOf.number.lessThan("featureId", featureId, this._featureCount);
  290. //>>includeEnd('debug');
  291. return this._ids[featureId];
  292. }
  293. /**
  294. * @param {number} featureId
  295. */
  296. getProperties(featureId) {
  297. //>>includeStart('debug', pragmas.debug);
  298. Check.typeOf.number.greaterThanOrEquals("featureId", featureId, 0);
  299. Check.typeOf.number.lessThan("featureId", featureId, this._featureCount);
  300. //>>includeEnd('debug');
  301. return this._properties[featureId];
  302. }
  303. /**
  304. * @param {FrameState} frameState
  305. * @private
  306. */
  307. update(frameState) {
  308. if (!this.show) {
  309. return;
  310. }
  311. if (defined(this._points)) {
  312. this._points.update(frameState);
  313. }
  314. if (defined(this._polylines)) {
  315. this._polylines.update(frameState);
  316. }
  317. if (defined(this._polygons)) {
  318. this._polygons.update(frameState);
  319. }
  320. }
  321. destroy() {
  322. if (this._points) {
  323. this._points.destroy();
  324. this._points = undefined;
  325. }
  326. if (this._polylines) {
  327. this._polylines.destroy();
  328. this._polylines = undefined;
  329. }
  330. if (this._polygons) {
  331. this._polygons.destroy();
  332. this._polygons = undefined;
  333. }
  334. return destroyObject(this);
  335. }
  336. isDestroyed() {
  337. return false;
  338. }
  339. }
  340. /**
  341. * @param {GeoJsonPrimitive} loader
  342. * @param {number} index
  343. * @param {{get: function(number, object): object}} collection
  344. * @param {function(new: object)} PrimitiveClass
  345. * @param {Record<string, unknown>} properties
  346. * @returns {object}
  347. * @ignore
  348. */
  349. function createPickObject(
  350. loader,
  351. index,
  352. collection,
  353. PrimitiveClass,
  354. properties,
  355. ) {
  356. if (defined(loader._pickObjectFactory)) {
  357. return loader._pickObjectFactory(index, collection, properties);
  358. }
  359. return {
  360. index,
  361. collection,
  362. get primitive() {
  363. // Cannot reuse primitives; scene.drillPick() appends to a list.
  364. return collection.get(index, new PrimitiveClass());
  365. },
  366. parentPrimitive: loader,
  367. properties,
  368. };
  369. }
  370. /**
  371. * @param {GeoJson} geoJson
  372. * @ignore
  373. */
  374. function parseGeoJson(geoJson) {
  375. const featureInputs = getInputFeatures(geoJson);
  376. /** @type {Array<{featureId:number, points:Array<GeoJsonPosition>, polylines:Array<Array<GeoJsonPosition>>, polygons:Array<{positions:Array<GeoJsonPosition>, holes:Uint32Array, triangles:Uint32Array}>}>} */
  377. const features = [];
  378. /** @type {Array<string|number|undefined>} */
  379. const ids = [];
  380. /** @type {Array<Record<string, unknown>>} */
  381. const properties = [];
  382. let pointCount = 0;
  383. let polylineCount = 0;
  384. let polylineVertexCount = 0;
  385. let polygonCount = 0;
  386. let polygonVertexCount = 0;
  387. let polygonHoleCount = 0;
  388. let polygonTriangleCount = 0;
  389. for (let i = 0; i < featureInputs.length; i++) {
  390. const featureInput = featureInputs[i];
  391. /** @type {{points: Array<GeoJsonPosition>, polylines: Array<Array<GeoJsonPosition>>, polygons: Array<{positions: Array<GeoJsonPosition>, holes: Uint32Array, triangles: Uint32Array}>}} */
  392. const featureGeometries = {
  393. points: [],
  394. polylines: [],
  395. polygons: [],
  396. };
  397. appendGeometry(featureInput.geometry, featureGeometries);
  398. if (
  399. featureGeometries.points.length === 0 &&
  400. featureGeometries.polylines.length === 0 &&
  401. featureGeometries.polygons.length === 0
  402. ) {
  403. continue;
  404. }
  405. const featureId = ids.length;
  406. ids.push(featureInput.id);
  407. properties.push(
  408. // @ts-expect-error Casting changes .d.ts output, a suspected bug in tsd-jsdoc.
  409. isPlainObject(featureInput.properties)
  410. ? featureInput.properties
  411. : Frozen.EMPTY_OBJECT,
  412. );
  413. for (let j = 0; j < featureGeometries.polygons.length; j++) {
  414. const polygon = featureGeometries.polygons[j];
  415. polygonHoleCount += polygon.holes.length;
  416. polygonTriangleCount += polygon.triangles.length / 3;
  417. polygonVertexCount += polygon.positions.length;
  418. }
  419. for (let j = 0; j < featureGeometries.polylines.length; j++) {
  420. polylineVertexCount += featureGeometries.polylines[j].length;
  421. }
  422. pointCount += featureGeometries.points.length;
  423. polylineCount += featureGeometries.polylines.length;
  424. polygonCount += featureGeometries.polygons.length;
  425. features.push({
  426. featureId: featureId,
  427. points: featureGeometries.points,
  428. polylines: featureGeometries.polylines,
  429. polygons: featureGeometries.polygons,
  430. });
  431. }
  432. return {
  433. features: features,
  434. ids: ids,
  435. properties: properties,
  436. pointCount: pointCount,
  437. polylineCount: polylineCount,
  438. polylineVertexCount: polylineVertexCount,
  439. polygonCount: polygonCount,
  440. polygonVertexCount: polygonVertexCount,
  441. polygonHoleCount: polygonHoleCount,
  442. polygonTriangleCount: polygonTriangleCount,
  443. };
  444. }
  445. /**
  446. * @param {GeoJson} geoJson
  447. * @returns {Array<GeoJsonFeature>}
  448. * @ignore
  449. */
  450. function getInputFeatures(geoJson) {
  451. if (!defined(geoJson) || !defined(geoJson.type)) {
  452. throw new RuntimeError("GeoJSON object must define 'type'.");
  453. }
  454. switch (geoJson.type) {
  455. case "FeatureCollection": {
  456. const fc = /** @type {{ features: GeoJsonFeature[] }} */ (geoJson);
  457. return fc.features;
  458. }
  459. case "Feature":
  460. return [/** @type {GeoJsonFeature} */ (geoJson)];
  461. default:
  462. if (isGeometryType(geoJson.type)) {
  463. return [
  464. {
  465. type: "Feature",
  466. geometry: /** @type {GeoJsonGeometry} */ (geoJson),
  467. properties: Frozen.EMPTY_OBJECT,
  468. id: undefined,
  469. },
  470. ];
  471. }
  472. throw new RuntimeError(`Unsupported GeoJSON type: ${geoJson.type}`);
  473. }
  474. }
  475. /**
  476. * @param {GeoJsonGeometry | null | undefined} geometry
  477. * @param {{points: Array<GeoJsonPosition>, polylines: Array<GeoJsonPosition[]>, polygons: Array<object>}} result
  478. * @ignore
  479. */
  480. function appendGeometry(geometry, result) {
  481. if (!defined(geometry) || !defined(geometry.type)) {
  482. return;
  483. }
  484. switch (geometry.type) {
  485. case "Point":
  486. appendPoint(geometry.coordinates, result.points);
  487. return;
  488. case "MultiPoint":
  489. appendMultiPoint(
  490. /** @type {unknown[]} */ (geometry.coordinates),
  491. result.points,
  492. );
  493. return;
  494. case "LineString":
  495. appendLineString(geometry.coordinates, result.polylines);
  496. return;
  497. case "MultiLineString":
  498. appendMultiLineString(
  499. /** @type {unknown[]} */ (geometry.coordinates),
  500. result.polylines,
  501. );
  502. return;
  503. case "Polygon":
  504. appendPolygon(geometry.coordinates, result.polygons);
  505. return;
  506. case "MultiPolygon":
  507. appendMultiPolygon(
  508. /** @type {unknown[]} */ (geometry.coordinates),
  509. result.polygons,
  510. );
  511. return;
  512. case "GeometryCollection":
  513. appendGeometryCollection(geometry.geometries, result);
  514. return;
  515. default:
  516. return;
  517. }
  518. }
  519. /**
  520. * @param {Array<GeoJsonGeometry>} geometries
  521. * @param {{points: Array<GeoJsonPosition>, polylines: Array<Array<GeoJsonPosition>>, polygons: Array<object>}} result
  522. * @ignore
  523. */
  524. function appendGeometryCollection(geometries, result) {
  525. if (!Array.isArray(geometries)) {
  526. return;
  527. }
  528. for (let i = 0; i < geometries.length; i++) {
  529. appendGeometry(geometries[i], result);
  530. }
  531. }
  532. /**
  533. * @param {unknown} coordinates
  534. * @param {Array<GeoJsonPosition>} points
  535. * @ignore
  536. */
  537. function appendPoint(coordinates, points) {
  538. const position = normalizePosition(coordinates);
  539. if (defined(position)) {
  540. points.push(position);
  541. }
  542. }
  543. /**
  544. * @param {Array.<unknown>} coordinates
  545. * @param {Array<GeoJsonPosition>} points
  546. * @ignore
  547. */
  548. function appendMultiPoint(coordinates, points) {
  549. for (let i = 0; i < coordinates.length; i++) {
  550. appendPoint(coordinates[i], points);
  551. }
  552. }
  553. /**
  554. * @param {unknown} coordinates
  555. * @param {Array<Array<GeoJsonPosition>>} polylines
  556. * @ignore
  557. */
  558. function appendLineString(coordinates, polylines) {
  559. const polyline = normalizeLine(coordinates);
  560. if (defined(polyline) && polyline.length >= 2) {
  561. polylines.push(polyline);
  562. }
  563. }
  564. /**
  565. * @param {Array.<unknown>} coordinates
  566. * @param {Array<Array<GeoJsonPosition>>} polylines
  567. * @ignore
  568. */
  569. function appendMultiLineString(coordinates, polylines) {
  570. for (let i = 0; i < coordinates.length; i++) {
  571. appendLineString(coordinates[i], polylines);
  572. }
  573. }
  574. /**
  575. * @param {unknown} coordinates
  576. * @param {Array<object>} polygons
  577. * @ignore
  578. */
  579. function appendPolygon(coordinates, polygons) {
  580. const polygon = normalizePolygon(coordinates);
  581. if (defined(polygon)) {
  582. polygons.push(polygon);
  583. }
  584. }
  585. /**
  586. * @param {Array.<unknown>} coordinates
  587. * @param {Array<object>} polygons
  588. * @ignore
  589. */
  590. function appendMultiPolygon(coordinates, polygons) {
  591. for (let i = 0; i < coordinates.length; i++) {
  592. appendPolygon(coordinates[i], polygons);
  593. }
  594. }
  595. /**
  596. * @param {unknown} coordinates
  597. * @returns {Array<GeoJsonPosition> | undefined}
  598. * @ignore
  599. */
  600. function normalizeLine(coordinates) {
  601. if (!Array.isArray(coordinates)) {
  602. return undefined;
  603. }
  604. const line = [];
  605. for (let i = 0; i < coordinates.length; i++) {
  606. const position = normalizePosition(coordinates[i]);
  607. if (defined(position)) {
  608. line.push(position);
  609. }
  610. }
  611. return line.length >= 2 ? line : undefined;
  612. }
  613. /**
  614. * @param {unknown} rings
  615. * @returns {{positions: Array<GeoJsonPosition>, holes: Uint32Array, triangles: Uint32Array} | undefined}
  616. * @ignore
  617. */
  618. function normalizePolygon(rings) {
  619. if (!Array.isArray(rings) || rings.length === 0) {
  620. return undefined;
  621. }
  622. const normalizedRings = [];
  623. for (let i = 0; i < rings.length; i++) {
  624. const ring = normalizeRing(rings[i]);
  625. if (defined(ring)) {
  626. normalizedRings.push(ring);
  627. }
  628. }
  629. if (normalizedRings.length === 0) {
  630. return undefined;
  631. }
  632. const outerRing = normalizedRings[0];
  633. if (outerRing.length < 3) {
  634. return undefined;
  635. }
  636. const positions2D = [];
  637. const positions = [];
  638. const holes = [];
  639. for (let i = 0; i < normalizedRings.length; i++) {
  640. const ring = normalizedRings[i];
  641. if (ring.length < 3) {
  642. continue;
  643. }
  644. if (i > 0) {
  645. holes.push(positions.length);
  646. }
  647. for (let j = 0; j < ring.length; j++) {
  648. const position = ring[j];
  649. positions.push(position);
  650. positions2D.push(new Cartesian2(position[0], position[1]));
  651. }
  652. }
  653. if (positions.length < 3) {
  654. return undefined;
  655. }
  656. const triangles = PolygonPipeline.triangulate(positions2D, holes);
  657. if (!defined(triangles) || triangles.length < 3) {
  658. return undefined;
  659. }
  660. return {
  661. positions: positions,
  662. holes: new Uint32Array(holes),
  663. triangles: new Uint32Array(triangles),
  664. };
  665. }
  666. /**
  667. * @param {unknown} coordinates
  668. * @returns {Array<GeoJsonPosition> | undefined}
  669. * @ignore
  670. */
  671. function normalizeRing(coordinates) {
  672. if (!Array.isArray(coordinates)) {
  673. return undefined;
  674. }
  675. const ring = [];
  676. for (let i = 0; i < coordinates.length; i++) {
  677. const position = normalizePosition(coordinates[i]);
  678. if (defined(position)) {
  679. ring.push(position);
  680. }
  681. }
  682. if (ring.length < 3) {
  683. return undefined;
  684. }
  685. if (ring.length > 1 && samePosition(ring[0], ring[ring.length - 1])) {
  686. // GeoJSON rings require the first and last position to be identical.
  687. // BufferPolygonCollection uses LINE_LOOP topology and prohibits duplicate
  688. // start/end vertices, so we remove the closing duplicate here.
  689. ring.pop();
  690. }
  691. return ring.length >= 3 ? ring : undefined;
  692. }
  693. /**
  694. * @param {unknown} coordinates
  695. * @returns {GeoJsonPosition | undefined}
  696. * @ignore
  697. */
  698. function normalizePosition(coordinates) {
  699. if (!Array.isArray(coordinates) || coordinates.length < 2) {
  700. return undefined;
  701. }
  702. const longitude = coordinates[0];
  703. const latitude = coordinates[1];
  704. const height = coordinates[2] ?? 0.0;
  705. if (
  706. !Number.isFinite(longitude) ||
  707. !Number.isFinite(latitude) ||
  708. !Number.isFinite(height)
  709. ) {
  710. return undefined;
  711. }
  712. return [longitude, latitude, height];
  713. }
  714. /**
  715. * @param {GeoJsonPosition} left
  716. * @param {GeoJsonPosition} right
  717. * @ignore
  718. */
  719. function samePosition(left, right) {
  720. return left[0] === right[0] && left[1] === right[1] && left[2] === right[2];
  721. }
  722. /**
  723. * @param {string} type
  724. * @returns {boolean}
  725. * @ignore
  726. */
  727. function isGeometryType(type) {
  728. return (
  729. type === "Point" ||
  730. type === "MultiPoint" ||
  731. type === "LineString" ||
  732. type === "MultiLineString" ||
  733. type === "Polygon" ||
  734. type === "MultiPolygon" ||
  735. type === "GeometryCollection"
  736. );
  737. }
  738. /**
  739. * @param {GeoJsonPosition} position
  740. * @param {Ellipsoid} ellipsoid
  741. * @param {Cartesian3} result
  742. * @returns {Cartesian3}
  743. * @ignore
  744. */
  745. function toCartesian(position, ellipsoid, result) {
  746. return Cartesian3.fromDegrees(
  747. position[0],
  748. position[1],
  749. position[2] ?? 0,
  750. ellipsoid,
  751. result,
  752. );
  753. }
  754. const scratchCartesian = new Cartesian3();
  755. /**
  756. * Packs positions into a reusable scratch typed array and returns a subarray
  757. * view matching the required length. Callers may reuse the underlying scratch
  758. * buffer after collection.add(), since values are copied into collection memory.
  759. *
  760. * @param {Array<GeoJsonPosition>} positions
  761. * @param {Ellipsoid} ellipsoid
  762. * @param {function(number):Float64Array} getScratch
  763. * @returns {Float64Array}
  764. * @ignore
  765. */
  766. function packPositionsToScratch(positions, ellipsoid, getScratch) {
  767. const requiredLength = positions.length * 3;
  768. const packed = getScratch(requiredLength);
  769. for (let i = 0; i < positions.length; i++) {
  770. const cartesian = toCartesian(positions[i], ellipsoid, scratchCartesian);
  771. packed[i * 3] = cartesian.x;
  772. packed[i * 3 + 1] = cartesian.y;
  773. packed[i * 3 + 2] = cartesian.z;
  774. }
  775. return packed.subarray(0, requiredLength);
  776. }
  777. /**
  778. * @param {unknown} value
  779. * @returns {boolean}
  780. * @ignore
  781. */
  782. function isPlainObject(value) {
  783. return typeof value === "object" && value !== null && !Array.isArray(value);
  784. }
  785. export default GeoJsonPrimitive;