Source: util/measure/LengthMeasurer.js

  1. /*
  2. * Copyright 2003-2006, 2009, 2017, 2020 United States Government, as represented
  3. * by the Administrator of the National Aeronautics and Space Administration.
  4. * All rights reserved.
  5. *
  6. * The NASAWorldWind/WebWorldWind platform is licensed under the Apache License,
  7. * Version 2.0 (the "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License
  9. * at http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software distributed
  12. * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  13. * CONDITIONS OF ANY KIND, either express or implied. See the License for the
  14. * specific language governing permissions and limitations under the License.
  15. *
  16. * NASAWorldWind/WebWorldWind also contains the following 3rd party Open Source
  17. * software:
  18. *
  19. * ES6-Promise – under MIT License
  20. * libtess.js – SGI Free Software License B
  21. * Proj4 – under MIT License
  22. * JSZip – under MIT License
  23. *
  24. * A complete listing of 3rd Party software notices and licenses included in
  25. * WebWorldWind can be found in the WebWorldWind 3rd-party notices and licenses
  26. * PDF found in code directory.
  27. */
  28. /**
  29. * @exports LengthMeasurer
  30. */
  31. define([
  32. '../../error/ArgumentError',
  33. '../../geom/Location',
  34. '../Logger',
  35. './MeasurerUtils',
  36. '../../geom/Position',
  37. '../../geom/Vec3'
  38. ],
  39. function (ArgumentError,
  40. Location,
  41. Logger,
  42. MeasurerUtils,
  43. Position,
  44. Vec3) {
  45. /**
  46. * Utility class to measure length along a path on a globe. <p/> <p>Segments which are longer then the current
  47. * maxSegmentLength will be subdivided along lines following the current pathType - WorldWind.LINEAR,
  48. * WorldWind.RHUMB_LINE or WorldWind.GREAT_CIRCLE.</p> <p/> <p>For follow terrain, the computed length will
  49. * account for terrain deformations as if someone was walking along that path. Otherwise the length is the sum
  50. * of the cartesian distance between the positions.</p>
  51. * <p/>
  52. * <p>When following terrain the measurer will sample terrain elevations at regular intervals along the path.
  53. * The minimum number of samples used for the whole length can be set with lengthTerrainSamplingSteps.
  54. * However, the minimum sampling interval is 30 meters.
  55. * @alias LengthMeasurer
  56. * @constructor
  57. * @param {WorldWindow} wwd The WorldWindow associated with LengthMeasurer.
  58. * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
  59. */
  60. var LengthMeasurer = function (wwd) {
  61. if (!wwd) {
  62. throw new ArgumentError(
  63. Logger.logMessage(Logger.LEVEL_SEVERE, "LengthMeasurer", "constructor", "missingWorldWindow"));
  64. }
  65. this.wwd = wwd;
  66. // Private. The minimum length of a terrain following subdivision.
  67. this.DEFAULT_MIN_SEGMENT_LENGTH = 30;
  68. // Private. Documentation is with the defined property below.
  69. this._maxSegmentLength = 100e3;
  70. // Private. Documentation is with the defined property below.
  71. this._lengthTerrainSamplingSteps = 128;
  72. // Private. A list of positions with no segment longer then maxLength and elevations following terrain or not.
  73. this.subdividedPositions = null;
  74. };
  75. Object.defineProperties(LengthMeasurer.prototype, {
  76. /**
  77. * The maximum length a segment can have before being subdivided along a line following the current pathType.
  78. * @type {Number}
  79. * @memberof LengthMeasurer.prototype
  80. */
  81. maxSegmentLength: {
  82. get: function () {
  83. return this._maxSegmentLength;
  84. },
  85. set: function (value) {
  86. this._maxSegmentLength = value;
  87. }
  88. },
  89. /**
  90. * The number of terrain elevation samples used along the path to approximate it's terrain following length.
  91. * @type {Number}
  92. * @memberof LengthMeasurer.prototype
  93. */
  94. lengthTerrainSamplingSteps: {
  95. get: function () {
  96. return this._lengthTerrainSamplingSteps;
  97. },
  98. set: function (value) {
  99. this._lengthTerrainSamplingSteps = value;
  100. }
  101. }
  102. });
  103. /**
  104. * Get the path length in meter. <p/> <p>If followTerrain is true, the computed length will account
  105. * for terrain deformations as if someone was walking along that path. Otherwise the length is the sum of the
  106. * cartesian distance between each positions.</p>
  107. *
  108. * @param {Position[]} positions
  109. * @param {Boolean} followTerrain
  110. * @param {String} pathType One of WorldWind.LINEAR, WorldWind.RHUMB_LINE or WorldWind.GREAT_CIRCLE
  111. *
  112. * @return the current path length or -1 if the position list is too short.
  113. */
  114. LengthMeasurer.prototype.getLength = function (positions, followTerrain, pathType) {
  115. pathType = pathType || WorldWind.GREAT_CIRCLE;
  116. this.subdividedPositions = null;
  117. return this.computeLength(positions, followTerrain, pathType);
  118. };
  119. /**
  120. * Get the path length in meter of a Path. <p/> <p>If the path's followTerrain is true, the computed length
  121. * will account for terrain deformations as if someone was walking along that path. Otherwise the length is the
  122. * sum of the cartesian distance between each positions.</p>
  123. *
  124. * @param {Path} path
  125. *
  126. * @return the current path length or -1 if the position list is too short.
  127. */
  128. LengthMeasurer.prototype.getPathLength = function (path) {
  129. this.subdividedPositions = null;
  130. return this.computeLength(path.positions, path.followTerrain, path.pathType);
  131. };
  132. /**
  133. * Get the great circle, rhumb or linear distance, in meter, of a Path or an array of Positions.
  134. *
  135. * @param {Path|Position[]} path A Path or an array of Positions
  136. * @param {String} pathType Optional argument used when path is an array of Positions.
  137. * Defaults to WorldWind.GREAT_CIRCLE.
  138. * Recognized values are:
  139. * <ul>
  140. * <li>[WorldWind.GREAT_CIRCLE]{@link WorldWind#GREAT_CIRCLE}</li>
  141. * <li>[WorldWind.RHUMB_LINE]{@link WorldWind#RHUMB_LINE}</li>
  142. * <li>[WorldWind.LINEAR]{@link WorldWind#LINEAR}</li>
  143. * </ul>
  144. *
  145. * @return {Number} the current path length or -1 if the position list is too short.
  146. */
  147. LengthMeasurer.prototype.getGeographicDistance = function (path, pathType) {
  148. if (path instanceof WorldWind.Path) {
  149. var positions = path.positions;
  150. var _pathType = path.pathType;
  151. }
  152. else if (Array.isArray(path)) {
  153. positions = path;
  154. _pathType = pathType || WorldWind.GREAT_CIRCLE;
  155. }
  156. if (!positions || positions.length < 2) {
  157. return -1;
  158. }
  159. var fn = Location.greatCircleDistance;
  160. if (_pathType === WorldWind.RHUMB_LINE) {
  161. fn = Location.rhumbDistance;
  162. }
  163. else if (_pathType === WorldWind.LINEAR) {
  164. fn = Location.linearDistance;
  165. }
  166. var distance = 0;
  167. for (var i = 0, len = positions.length - 1; i < len; i++) {
  168. var pos1 = positions[i];
  169. var pos2 = positions[i + 1];
  170. distance += fn(pos1, pos2);
  171. }
  172. return distance * this.wwd.globe.equatorialRadius;
  173. };
  174. /**
  175. * Computes the length.
  176. * @param {Position[]} positions
  177. * @param {Boolean} followTerrain
  178. * @param {String} pathType One of WorldWind.LINEAR, WorldWind.RHUMB_LINE or WorldWind.GREAT_CIRCLE
  179. */
  180. LengthMeasurer.prototype.computeLength = function (positions, followTerrain, pathType) {
  181. if (!positions || positions.length < 2) {
  182. return -1;
  183. }
  184. var globe = this.wwd.globe;
  185. if (this.subdividedPositions == null) {
  186. // Subdivide path so as to have at least segments smaller then maxSegmentLength. If follow terrain,
  187. // subdivide so as to have at least lengthTerrainSamplingSteps segments, but no segments shorter then
  188. // DEFAULT_MIN_SEGMENT_LENGTH either.
  189. var maxLength = this._maxSegmentLength;
  190. if (followTerrain) {
  191. // Recurse to compute overall path length not following terrain
  192. var pathLength = this.computeLength(positions, false, pathType);
  193. // Determine segment length to have enough sampling points
  194. maxLength = pathLength / this._lengthTerrainSamplingSteps;
  195. maxLength = Math.min(Math.max(maxLength, this.DEFAULT_MIN_SEGMENT_LENGTH), this._maxSegmentLength);
  196. }
  197. this.subdividedPositions = MeasurerUtils.subdividePositions(globe, positions, followTerrain, pathType,
  198. maxLength);
  199. }
  200. var distance = 0;
  201. var pos0 = this.subdividedPositions[0];
  202. var p1 = new Vec3(0, 0, 0);
  203. var p2 = new Vec3(0, 0, 0);
  204. p1 = globe.computePointFromPosition(pos0.latitude, pos0.longitude, pos0.altitude, p1);
  205. for (var i = 1, len = this.subdividedPositions.length; i < len; i++) {
  206. var pos = this.subdividedPositions[i];
  207. p2 = globe.computePointFromPosition(pos.latitude, pos.longitude, pos.altitude, p2);
  208. distance += p1.distanceTo(p2);
  209. p1.copy(p2);
  210. }
  211. return distance;
  212. };
  213. return LengthMeasurer;
  214. });