Source: projections/ProjectionMercator.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 ProjectionMercator
  30. */
  31. define([
  32. '../geom/Angle',
  33. '../error/ArgumentError',
  34. '../projections/GeographicProjection',
  35. '../util/Logger',
  36. '../geom/Sector',
  37. '../geom/Vec3',
  38. '../util/WWMath'
  39. ],
  40. function (Angle,
  41. ArgumentError,
  42. GeographicProjection,
  43. Logger,
  44. Sector,
  45. Vec3,
  46. WWMath) {
  47. "use strict";
  48. /**
  49. * Constructs a Mercator geographic projection.
  50. * @alias ProjectionMercator
  51. * @constructor
  52. * @augments GeographicProjection
  53. * @classdesc Represents a Mercator geographic projection.
  54. */
  55. var ProjectionMercator = function () {
  56. GeographicProjection.call(this, "Mercator", true, new Sector(-78, 78, -180, 180));
  57. };
  58. ProjectionMercator.prototype = Object.create(GeographicProjection.prototype);
  59. // Documented in base class.
  60. ProjectionMercator.prototype.geographicToCartesian = function (globe, latitude, longitude, elevation, offset,
  61. result) {
  62. if (!globe) {
  63. throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
  64. "geographicToCartesian", "missingGlobe"));
  65. }
  66. if (!result) {
  67. throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
  68. "geographicToCartesian", "missingResult"));
  69. }
  70. if (latitude > this.projectionLimits.maxLatitude) {
  71. latitude = this.projectionLimits.maxLatitude;
  72. }
  73. if (latitude < this.projectionLimits.minLatitude) {
  74. latitude = this.projectionLimits.minLatitude;
  75. }
  76. // See "Map Projections: A Working Manual", page 44 for the source of the below formulas.
  77. var ecc = Math.sqrt(globe.eccentricitySquared),
  78. sinLat = Math.sin(latitude * Angle.DEGREES_TO_RADIANS),
  79. s = ((1 + sinLat) / (1 - sinLat)) * Math.pow((1 - ecc * sinLat) / (1 + ecc * sinLat), ecc);
  80. result[0] = globe.equatorialRadius * longitude * Angle.DEGREES_TO_RADIANS + (offset ? offset[0] : 0);
  81. result[1] = 0.5 * globe.equatorialRadius * Math.log(s);
  82. result[2] = elevation;
  83. return result;
  84. };
  85. Object.defineProperties(ProjectionMercator.prototype, {
  86. /**
  87. * A string identifying this projection's current state. Used to compare states during rendering to
  88. * determine whether globe-state dependent cached values must be updated. Applications typically do not
  89. * interact with this property.
  90. * @memberof ProjectionMercator.prototype
  91. * @readonly
  92. * @type {String}
  93. */
  94. stateKey: {
  95. get: function () {
  96. return "projection mercator ";
  97. }
  98. }
  99. });
  100. // Documented in base class.
  101. ProjectionMercator.prototype.geographicToCartesianGrid = function (globe, sector, numLat, numLon, elevations,
  102. referencePoint, offset, result) {
  103. if (!globe) {
  104. throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
  105. "geographicToCartesianGrid", "missingGlobe"));
  106. }
  107. if (!sector) {
  108. throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
  109. "geographicToCartesianGrid", "missingSector"));
  110. }
  111. if (!elevations || elevations.length < numLat * numLon) {
  112. throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
  113. "geographicToCartesianGrid",
  114. "The specified elevations array is null, undefined or insufficient length"));
  115. }
  116. if (!result) {
  117. throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
  118. "geographicToCartesianGrid", "missingResult"));
  119. }
  120. var eqr = globe.equatorialRadius,
  121. ecc = Math.sqrt(globe.eccentricitySquared),
  122. minLat = sector.minLatitude * Angle.DEGREES_TO_RADIANS,
  123. maxLat = sector.maxLatitude * Angle.DEGREES_TO_RADIANS,
  124. minLon = sector.minLongitude * Angle.DEGREES_TO_RADIANS,
  125. maxLon = sector.maxLongitude * Angle.DEGREES_TO_RADIANS,
  126. deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1),
  127. deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1),
  128. minLatLimit = this.projectionLimits.minLatitude * Angle.DEGREES_TO_RADIANS,
  129. maxLatLimit = this.projectionLimits.maxLatitude * Angle.DEGREES_TO_RADIANS,
  130. refCenter = referencePoint ? referencePoint : new Vec3(0, 0, 0),
  131. offsetX = offset ? offset[0] : 0,
  132. latIndex, lonIndex,
  133. elevIndex = 0, resultIndex = 0,
  134. lat, lon, clampedLat, sinLat, s, y;
  135. // Iterate over the latitude and longitude coordinates in the specified sector, computing the Cartesian point
  136. // corresponding to each latitude and longitude.
  137. for (latIndex = 0, lat = minLat; latIndex < numLat; latIndex++, lat += deltaLat) {
  138. if (latIndex === numLat - 1) {
  139. lat = maxLat; // explicitly set the last lat to the max latitude to ensure alignment
  140. }
  141. // Latitude is constant for each row. Values that are a function of latitude can be computed once per row.
  142. clampedLat = WWMath.clamp(lat, minLatLimit, maxLatLimit);
  143. sinLat = Math.sin(clampedLat);
  144. s = ((1 + sinLat) / (1 - sinLat)) * Math.pow((1 - ecc * sinLat) / (1 + ecc * sinLat), ecc);
  145. y = eqr * Math.log(s) * 0.5 - refCenter[1];
  146. for (lonIndex = 0, lon = minLon; lonIndex < numLon; lonIndex++, lon += deltaLon) {
  147. if (lonIndex === numLon - 1) {
  148. lon = maxLon; // explicitly set the last lon to the max longitude to ensure alignment
  149. }
  150. result[resultIndex++] = eqr * lon - refCenter[0] + offsetX;
  151. result[resultIndex++] = y;
  152. result[resultIndex++] = elevations[elevIndex++] - refCenter[2];
  153. }
  154. }
  155. return result;
  156. };
  157. // Documented in base class.
  158. ProjectionMercator.prototype.cartesianToGeographic = function (globe, x, y, z, offset, result) {
  159. if (!globe) {
  160. throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
  161. "cartesianToGeographic", "missingGlobe"));
  162. }
  163. if (!result) {
  164. throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionMercator",
  165. "cartesianToGeographic", "missingResult"));
  166. }
  167. // See "Map Projections: A Working Manual", pages 45 and 19 for the source of the below formulas.
  168. var ecc2 = globe.eccentricitySquared,
  169. ecc4 = ecc2 * ecc2,
  170. ecc6 = ecc4 * ecc2,
  171. ecc8 = ecc6 * ecc2,
  172. t = Math.pow(Math.E, - y / globe.equatorialRadius),
  173. A = Math.PI / 2 - 2 * Math.atan(t),
  174. B = ecc2 / 2 + 5 * ecc4 / 24 + ecc6 / 12 + 13 * ecc8 / 360,
  175. C = 7 * ecc4 / 48 + 29 * ecc6 / 240 + 811 * ecc8 / 11520,
  176. D = 7 * ecc6 / 120 + 81 * ecc8 / 1120,
  177. E = 4279 * ecc8 / 161280,
  178. Ap = A - C + E,
  179. Bp = B - 3 * D,
  180. Cp = 2 * C - 8 * E,
  181. Dp = 4 * D,
  182. Ep = 8 * E,
  183. s2p = Math.sin(2 * A),
  184. lat = Ap + s2p * (Bp + s2p * (Cp + s2p * (Dp + Ep * s2p)));
  185. result.latitude = lat * Angle.RADIANS_TO_DEGREES;
  186. result.longitude = ((x - (offset ? offset[0] : 0)) / globe.equatorialRadius) * Angle.RADIANS_TO_DEGREES;
  187. result.altitude = z;
  188. return result;
  189. };
  190. return ProjectionMercator;
  191. });