PolylinePipeline.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. import Cartesian3 from "./Cartesian3.js";
  2. import Cartographic from "./Cartographic.js";
  3. import defaultValue from "./defaultValue.js";
  4. import defined from "./defined.js";
  5. import DeveloperError from "./DeveloperError.js";
  6. import Ellipsoid from "./Ellipsoid.js";
  7. import EllipsoidGeodesic from "./EllipsoidGeodesic.js";
  8. import EllipsoidRhumbLine from "./EllipsoidRhumbLine.js";
  9. import IntersectionTests from "./IntersectionTests.js";
  10. import CesiumMath from "./Math.js";
  11. import Matrix4 from "./Matrix4.js";
  12. import Plane from "./Plane.js";
  13. /**
  14. * @private
  15. */
  16. const PolylinePipeline = {};
  17. PolylinePipeline.numberOfPoints = function (p0, p1, minDistance) {
  18. const distance = Cartesian3.distance(p0, p1);
  19. return Math.ceil(distance / minDistance);
  20. };
  21. PolylinePipeline.numberOfPointsRhumbLine = function (p0, p1, granularity) {
  22. const radiansDistanceSquared =
  23. Math.pow(p0.longitude - p1.longitude, 2) +
  24. Math.pow(p0.latitude - p1.latitude, 2);
  25. return Math.max(
  26. 1,
  27. Math.ceil(Math.sqrt(radiansDistanceSquared / (granularity * granularity)))
  28. );
  29. };
  30. const cartoScratch = new Cartographic();
  31. PolylinePipeline.extractHeights = function (positions, ellipsoid) {
  32. const length = positions.length;
  33. const heights = new Array(length);
  34. for (let i = 0; i < length; i++) {
  35. const p = positions[i];
  36. heights[i] = ellipsoid.cartesianToCartographic(p, cartoScratch).height;
  37. }
  38. return heights;
  39. };
  40. const wrapLongitudeInversMatrix = new Matrix4();
  41. const wrapLongitudeOrigin = new Cartesian3();
  42. const wrapLongitudeXZNormal = new Cartesian3();
  43. const wrapLongitudeXZPlane = new Plane(Cartesian3.UNIT_X, 0.0);
  44. const wrapLongitudeYZNormal = new Cartesian3();
  45. const wrapLongitudeYZPlane = new Plane(Cartesian3.UNIT_X, 0.0);
  46. const wrapLongitudeIntersection = new Cartesian3();
  47. const wrapLongitudeOffset = new Cartesian3();
  48. const subdivideHeightsScratchArray = [];
  49. function subdivideHeights(numPoints, h0, h1) {
  50. const heights = subdivideHeightsScratchArray;
  51. heights.length = numPoints;
  52. let i;
  53. if (h0 === h1) {
  54. for (i = 0; i < numPoints; i++) {
  55. heights[i] = h0;
  56. }
  57. return heights;
  58. }
  59. const dHeight = h1 - h0;
  60. const heightPerVertex = dHeight / numPoints;
  61. for (i = 0; i < numPoints; i++) {
  62. const h = h0 + i * heightPerVertex;
  63. heights[i] = h;
  64. }
  65. return heights;
  66. }
  67. const carto1 = new Cartographic();
  68. const carto2 = new Cartographic();
  69. const cartesian = new Cartesian3();
  70. const scaleFirst = new Cartesian3();
  71. const scaleLast = new Cartesian3();
  72. const ellipsoidGeodesic = new EllipsoidGeodesic();
  73. let ellipsoidRhumb = new EllipsoidRhumbLine();
  74. //Returns subdivided line scaled to ellipsoid surface starting at p1 and ending at p2.
  75. //Result includes p1, but not include p2. This function is called for a sequence of line segments,
  76. //and this prevents duplication of end point.
  77. function generateCartesianArc(
  78. p0,
  79. p1,
  80. minDistance,
  81. ellipsoid,
  82. h0,
  83. h1,
  84. array,
  85. offset
  86. ) {
  87. const first = ellipsoid.scaleToGeodeticSurface(p0, scaleFirst);
  88. const last = ellipsoid.scaleToGeodeticSurface(p1, scaleLast);
  89. const numPoints = PolylinePipeline.numberOfPoints(p0, p1, minDistance);
  90. const start = ellipsoid.cartesianToCartographic(first, carto1);
  91. const end = ellipsoid.cartesianToCartographic(last, carto2);
  92. const heights = subdivideHeights(numPoints, h0, h1);
  93. ellipsoidGeodesic.setEndPoints(start, end);
  94. const surfaceDistanceBetweenPoints =
  95. ellipsoidGeodesic.surfaceDistance / numPoints;
  96. let index = offset;
  97. start.height = h0;
  98. let cart = ellipsoid.cartographicToCartesian(start, cartesian);
  99. Cartesian3.pack(cart, array, index);
  100. index += 3;
  101. for (let i = 1; i < numPoints; i++) {
  102. const carto = ellipsoidGeodesic.interpolateUsingSurfaceDistance(
  103. i * surfaceDistanceBetweenPoints,
  104. carto2
  105. );
  106. carto.height = heights[i];
  107. cart = ellipsoid.cartographicToCartesian(carto, cartesian);
  108. Cartesian3.pack(cart, array, index);
  109. index += 3;
  110. }
  111. return index;
  112. }
  113. //Returns subdivided line scaled to ellipsoid surface starting at p1 and ending at p2.
  114. //Result includes p1, but not include p2. This function is called for a sequence of line segments,
  115. //and this prevents duplication of end point.
  116. function generateCartesianRhumbArc(
  117. p0,
  118. p1,
  119. granularity,
  120. ellipsoid,
  121. h0,
  122. h1,
  123. array,
  124. offset
  125. ) {
  126. const start = ellipsoid.cartesianToCartographic(p0, carto1);
  127. const end = ellipsoid.cartesianToCartographic(p1, carto2);
  128. const numPoints = PolylinePipeline.numberOfPointsRhumbLine(
  129. start,
  130. end,
  131. granularity
  132. );
  133. start.height = 0.0;
  134. end.height = 0.0;
  135. const heights = subdivideHeights(numPoints, h0, h1);
  136. if (!ellipsoidRhumb.ellipsoid.equals(ellipsoid)) {
  137. ellipsoidRhumb = new EllipsoidRhumbLine(undefined, undefined, ellipsoid);
  138. }
  139. ellipsoidRhumb.setEndPoints(start, end);
  140. const surfaceDistanceBetweenPoints =
  141. ellipsoidRhumb.surfaceDistance / numPoints;
  142. let index = offset;
  143. start.height = h0;
  144. let cart = ellipsoid.cartographicToCartesian(start, cartesian);
  145. Cartesian3.pack(cart, array, index);
  146. index += 3;
  147. for (let i = 1; i < numPoints; i++) {
  148. const carto = ellipsoidRhumb.interpolateUsingSurfaceDistance(
  149. i * surfaceDistanceBetweenPoints,
  150. carto2
  151. );
  152. carto.height = heights[i];
  153. cart = ellipsoid.cartographicToCartesian(carto, cartesian);
  154. Cartesian3.pack(cart, array, index);
  155. index += 3;
  156. }
  157. return index;
  158. }
  159. /**
  160. * Breaks a {@link Polyline} into segments such that it does not cross the &plusmn;180 degree meridian of an ellipsoid.
  161. *
  162. * @param {Cartesian3[]} positions The polyline's Cartesian positions.
  163. * @param {Matrix4} [modelMatrix=Matrix4.IDENTITY] The polyline's model matrix. Assumed to be an affine
  164. * transformation matrix, where the upper left 3x3 elements are a rotation matrix, and
  165. * the upper three elements in the fourth column are the translation. The bottom row is assumed to be [0, 0, 0, 1].
  166. * The matrix is not verified to be in the proper form.
  167. * @returns {Object} An object with a <code>positions</code> property that is an array of positions and a
  168. * <code>segments</code> property.
  169. *
  170. *
  171. * @example
  172. * const polylines = new Cesium.PolylineCollection();
  173. * const polyline = polylines.add(...);
  174. * const positions = polyline.positions;
  175. * const modelMatrix = polylines.modelMatrix;
  176. * const segments = Cesium.PolylinePipeline.wrapLongitude(positions, modelMatrix);
  177. *
  178. * @see PolygonPipeline.wrapLongitude
  179. * @see Polyline
  180. * @see PolylineCollection
  181. */
  182. PolylinePipeline.wrapLongitude = function (positions, modelMatrix) {
  183. const cartesians = [];
  184. const segments = [];
  185. if (defined(positions) && positions.length > 0) {
  186. modelMatrix = defaultValue(modelMatrix, Matrix4.IDENTITY);
  187. const inverseModelMatrix = Matrix4.inverseTransformation(
  188. modelMatrix,
  189. wrapLongitudeInversMatrix
  190. );
  191. const origin = Matrix4.multiplyByPoint(
  192. inverseModelMatrix,
  193. Cartesian3.ZERO,
  194. wrapLongitudeOrigin
  195. );
  196. const xzNormal = Cartesian3.normalize(
  197. Matrix4.multiplyByPointAsVector(
  198. inverseModelMatrix,
  199. Cartesian3.UNIT_Y,
  200. wrapLongitudeXZNormal
  201. ),
  202. wrapLongitudeXZNormal
  203. );
  204. const xzPlane = Plane.fromPointNormal(
  205. origin,
  206. xzNormal,
  207. wrapLongitudeXZPlane
  208. );
  209. const yzNormal = Cartesian3.normalize(
  210. Matrix4.multiplyByPointAsVector(
  211. inverseModelMatrix,
  212. Cartesian3.UNIT_X,
  213. wrapLongitudeYZNormal
  214. ),
  215. wrapLongitudeYZNormal
  216. );
  217. const yzPlane = Plane.fromPointNormal(
  218. origin,
  219. yzNormal,
  220. wrapLongitudeYZPlane
  221. );
  222. let count = 1;
  223. cartesians.push(Cartesian3.clone(positions[0]));
  224. let prev = cartesians[0];
  225. const length = positions.length;
  226. for (let i = 1; i < length; ++i) {
  227. const cur = positions[i];
  228. // intersects the IDL if either endpoint is on the negative side of the yz-plane
  229. if (
  230. Plane.getPointDistance(yzPlane, prev) < 0.0 ||
  231. Plane.getPointDistance(yzPlane, cur) < 0.0
  232. ) {
  233. // and intersects the xz-plane
  234. const intersection = IntersectionTests.lineSegmentPlane(
  235. prev,
  236. cur,
  237. xzPlane,
  238. wrapLongitudeIntersection
  239. );
  240. if (defined(intersection)) {
  241. // move point on the xz-plane slightly away from the plane
  242. const offset = Cartesian3.multiplyByScalar(
  243. xzNormal,
  244. 5.0e-9,
  245. wrapLongitudeOffset
  246. );
  247. if (Plane.getPointDistance(xzPlane, prev) < 0.0) {
  248. Cartesian3.negate(offset, offset);
  249. }
  250. cartesians.push(
  251. Cartesian3.add(intersection, offset, new Cartesian3())
  252. );
  253. segments.push(count + 1);
  254. Cartesian3.negate(offset, offset);
  255. cartesians.push(
  256. Cartesian3.add(intersection, offset, new Cartesian3())
  257. );
  258. count = 1;
  259. }
  260. }
  261. cartesians.push(Cartesian3.clone(positions[i]));
  262. count++;
  263. prev = cur;
  264. }
  265. segments.push(count);
  266. }
  267. return {
  268. positions: cartesians,
  269. lengths: segments,
  270. };
  271. };
  272. /**
  273. * Subdivides polyline and raises all points to the specified height. Returns an array of numbers to represent the positions.
  274. * @param {Object} options Object with the following properties:
  275. * @param {Cartesian3[]} options.positions The array of type {Cartesian3} representing positions.
  276. * @param {Number|Number[]} [options.height=0.0] A number or array of numbers representing the heights of each position.
  277. * @param {Number} [options.granularity = CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
  278. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
  279. * @returns {Number[]} A new array of positions of type {Number} that have been subdivided and raised to the surface of the ellipsoid.
  280. *
  281. * @example
  282. * const positions = Cesium.Cartesian3.fromDegreesArray([
  283. * -105.0, 40.0,
  284. * -100.0, 38.0,
  285. * -105.0, 35.0,
  286. * -100.0, 32.0
  287. * ]);
  288. * const surfacePositions = Cesium.PolylinePipeline.generateArc({
  289. * positons: positions
  290. * });
  291. */
  292. PolylinePipeline.generateArc = function (options) {
  293. if (!defined(options)) {
  294. options = {};
  295. }
  296. const positions = options.positions;
  297. //>>includeStart('debug', pragmas.debug);
  298. if (!defined(positions)) {
  299. throw new DeveloperError("options.positions is required.");
  300. }
  301. //>>includeEnd('debug');
  302. const length = positions.length;
  303. const ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
  304. let height = defaultValue(options.height, 0);
  305. const hasHeightArray = Array.isArray(height);
  306. if (length < 1) {
  307. return [];
  308. } else if (length === 1) {
  309. const p = ellipsoid.scaleToGeodeticSurface(positions[0], scaleFirst);
  310. height = hasHeightArray ? height[0] : height;
  311. if (height !== 0) {
  312. const n = ellipsoid.geodeticSurfaceNormal(p, cartesian);
  313. Cartesian3.multiplyByScalar(n, height, n);
  314. Cartesian3.add(p, n, p);
  315. }
  316. return [p.x, p.y, p.z];
  317. }
  318. let minDistance = options.minDistance;
  319. if (!defined(minDistance)) {
  320. const granularity = defaultValue(
  321. options.granularity,
  322. CesiumMath.RADIANS_PER_DEGREE
  323. );
  324. minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
  325. }
  326. let numPoints = 0;
  327. let i;
  328. for (i = 0; i < length - 1; i++) {
  329. numPoints += PolylinePipeline.numberOfPoints(
  330. positions[i],
  331. positions[i + 1],
  332. minDistance
  333. );
  334. }
  335. const arrayLength = (numPoints + 1) * 3;
  336. const newPositions = new Array(arrayLength);
  337. let offset = 0;
  338. for (i = 0; i < length - 1; i++) {
  339. const p0 = positions[i];
  340. const p1 = positions[i + 1];
  341. const h0 = hasHeightArray ? height[i] : height;
  342. const h1 = hasHeightArray ? height[i + 1] : height;
  343. offset = generateCartesianArc(
  344. p0,
  345. p1,
  346. minDistance,
  347. ellipsoid,
  348. h0,
  349. h1,
  350. newPositions,
  351. offset
  352. );
  353. }
  354. subdivideHeightsScratchArray.length = 0;
  355. const lastPoint = positions[length - 1];
  356. const carto = ellipsoid.cartesianToCartographic(lastPoint, carto1);
  357. carto.height = hasHeightArray ? height[length - 1] : height;
  358. const cart = ellipsoid.cartographicToCartesian(carto, cartesian);
  359. Cartesian3.pack(cart, newPositions, arrayLength - 3);
  360. return newPositions;
  361. };
  362. const scratchCartographic0 = new Cartographic();
  363. const scratchCartographic1 = new Cartographic();
  364. /**
  365. * Subdivides polyline and raises all points to the specified height using Rhumb lines. Returns an array of numbers to represent the positions.
  366. * @param {Object} options Object with the following properties:
  367. * @param {Cartesian3[]} options.positions The array of type {Cartesian3} representing positions.
  368. * @param {Number|Number[]} [options.height=0.0] A number or array of numbers representing the heights of each position.
  369. * @param {Number} [options.granularity = CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
  370. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
  371. * @returns {Number[]} A new array of positions of type {Number} that have been subdivided and raised to the surface of the ellipsoid.
  372. *
  373. * @example
  374. * const positions = Cesium.Cartesian3.fromDegreesArray([
  375. * -105.0, 40.0,
  376. * -100.0, 38.0,
  377. * -105.0, 35.0,
  378. * -100.0, 32.0
  379. * ]);
  380. * const surfacePositions = Cesium.PolylinePipeline.generateRhumbArc({
  381. * positons: positions
  382. * });
  383. */
  384. PolylinePipeline.generateRhumbArc = function (options) {
  385. if (!defined(options)) {
  386. options = {};
  387. }
  388. const positions = options.positions;
  389. //>>includeStart('debug', pragmas.debug);
  390. if (!defined(positions)) {
  391. throw new DeveloperError("options.positions is required.");
  392. }
  393. //>>includeEnd('debug');
  394. const length = positions.length;
  395. const ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
  396. let height = defaultValue(options.height, 0);
  397. const hasHeightArray = Array.isArray(height);
  398. if (length < 1) {
  399. return [];
  400. } else if (length === 1) {
  401. const p = ellipsoid.scaleToGeodeticSurface(positions[0], scaleFirst);
  402. height = hasHeightArray ? height[0] : height;
  403. if (height !== 0) {
  404. const n = ellipsoid.geodeticSurfaceNormal(p, cartesian);
  405. Cartesian3.multiplyByScalar(n, height, n);
  406. Cartesian3.add(p, n, p);
  407. }
  408. return [p.x, p.y, p.z];
  409. }
  410. const granularity = defaultValue(
  411. options.granularity,
  412. CesiumMath.RADIANS_PER_DEGREE
  413. );
  414. let numPoints = 0;
  415. let i;
  416. let c0 = ellipsoid.cartesianToCartographic(
  417. positions[0],
  418. scratchCartographic0
  419. );
  420. let c1;
  421. for (i = 0; i < length - 1; i++) {
  422. c1 = ellipsoid.cartesianToCartographic(
  423. positions[i + 1],
  424. scratchCartographic1
  425. );
  426. numPoints += PolylinePipeline.numberOfPointsRhumbLine(c0, c1, granularity);
  427. c0 = Cartographic.clone(c1, scratchCartographic0);
  428. }
  429. const arrayLength = (numPoints + 1) * 3;
  430. const newPositions = new Array(arrayLength);
  431. let offset = 0;
  432. for (i = 0; i < length - 1; i++) {
  433. const p0 = positions[i];
  434. const p1 = positions[i + 1];
  435. const h0 = hasHeightArray ? height[i] : height;
  436. const h1 = hasHeightArray ? height[i + 1] : height;
  437. offset = generateCartesianRhumbArc(
  438. p0,
  439. p1,
  440. granularity,
  441. ellipsoid,
  442. h0,
  443. h1,
  444. newPositions,
  445. offset
  446. );
  447. }
  448. subdivideHeightsScratchArray.length = 0;
  449. const lastPoint = positions[length - 1];
  450. const carto = ellipsoid.cartesianToCartographic(lastPoint, carto1);
  451. carto.height = hasHeightArray ? height[length - 1] : height;
  452. const cart = ellipsoid.cartographicToCartesian(carto, cartesian);
  453. Cartesian3.pack(cart, newPositions, arrayLength - 3);
  454. return newPositions;
  455. };
  456. /**
  457. * Subdivides polyline and raises all points to the specified height. Returns an array of new {Cartesian3} positions.
  458. * @param {Object} options Object with the following properties:
  459. * @param {Cartesian3[]} options.positions The array of type {Cartesian3} representing positions.
  460. * @param {Number|Number[]} [options.height=0.0] A number or array of numbers representing the heights of each position.
  461. * @param {Number} [options.granularity = CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
  462. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
  463. * @returns {Cartesian3[]} A new array of cartesian3 positions that have been subdivided and raised to the surface of the ellipsoid.
  464. *
  465. * @example
  466. * const positions = Cesium.Cartesian3.fromDegreesArray([
  467. * -105.0, 40.0,
  468. * -100.0, 38.0,
  469. * -105.0, 35.0,
  470. * -100.0, 32.0
  471. * ]);
  472. * const surfacePositions = Cesium.PolylinePipeline.generateCartesianArc({
  473. * positons: positions
  474. * });
  475. */
  476. PolylinePipeline.generateCartesianArc = function (options) {
  477. const numberArray = PolylinePipeline.generateArc(options);
  478. const size = numberArray.length / 3;
  479. const newPositions = new Array(size);
  480. for (let i = 0; i < size; i++) {
  481. newPositions[i] = Cartesian3.unpack(numberArray, i * 3);
  482. }
  483. return newPositions;
  484. };
  485. /**
  486. * Subdivides polyline and raises all points to the specified height using Rhumb Lines. Returns an array of new {Cartesian3} positions.
  487. * @param {Object} options Object with the following properties:
  488. * @param {Cartesian3[]} options.positions The array of type {Cartesian3} representing positions.
  489. * @param {Number|Number[]} [options.height=0.0] A number or array of numbers representing the heights of each position.
  490. * @param {Number} [options.granularity = CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
  491. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
  492. * @returns {Cartesian3[]} A new array of cartesian3 positions that have been subdivided and raised to the surface of the ellipsoid.
  493. *
  494. * @example
  495. * const positions = Cesium.Cartesian3.fromDegreesArray([
  496. * -105.0, 40.0,
  497. * -100.0, 38.0,
  498. * -105.0, 35.0,
  499. * -100.0, 32.0
  500. * ]);
  501. * const surfacePositions = Cesium.PolylinePipeline.generateCartesianRhumbArc({
  502. * positons: positions
  503. * });
  504. */
  505. PolylinePipeline.generateCartesianRhumbArc = function (options) {
  506. const numberArray = PolylinePipeline.generateRhumbArc(options);
  507. const size = numberArray.length / 3;
  508. const newPositions = new Array(size);
  509. for (let i = 0; i < size; i++) {
  510. newPositions[i] = Cartesian3.unpack(numberArray, i * 3);
  511. }
  512. return newPositions;
  513. };
  514. export default PolylinePipeline;