PolylinePipeline.js 20 KB

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