PolylinePipeline.js 18 KB

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