Source: layer/CoordinatesDisplayLayer.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 CoordinatesDisplayLayer
  30. */
  31. define([
  32. '../error/ArgumentError',
  33. '../util/Color',
  34. '../util/Font',
  35. '../layer/Layer',
  36. '../util/Logger',
  37. '../util/Offset',
  38. '../geom/Position',
  39. '../shapes/ScreenImage',
  40. '../shapes/ScreenText',
  41. '../shapes/TextAttributes',
  42. '../geom/Vec2'
  43. ],
  44. function (ArgumentError,
  45. Color,
  46. Font,
  47. Layer,
  48. Logger,
  49. Offset,
  50. Position,
  51. ScreenImage,
  52. ScreenText,
  53. TextAttributes,
  54. Vec2) {
  55. "use strict";
  56. /**
  57. * Constructs a layer that displays the current map coordinates.
  58. * @alias CoordinatesDisplayLayer
  59. * @constructor
  60. * @augments Layer
  61. * @classDesc Displays the current map coordinates. A coordinates display layer cannot be shared among World
  62. * Windows. Each WorldWindow if it is to have a coordinates display layer must have its own. See the
  63. * MultiWindow example for guidance.
  64. * @param {WorldWindow} worldWindow The WorldWindow associated with this layer.
  65. * This layer may not be associated with more than one WorldWindow. Each WorldWindow must have it's own
  66. * instance of this layer if each window is to have a coordinates display.
  67. * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
  68. */
  69. var CoordinatesDisplayLayer = function (worldWindow) {
  70. if (!worldWindow) {
  71. throw new ArgumentError(
  72. Logger.logMessage(Logger.LEVEL_SEVERE, "CoordinatesDisplayLayer", "constructor", "missingWorldWindow"));
  73. }
  74. Layer.call(this, "Coordinates");
  75. /**
  76. * The WorldWindow associated with this layer.
  77. * @type {WorldWindow}
  78. * @readonly
  79. */
  80. this.wwd = worldWindow;
  81. // No picking of this layer's screen elements.
  82. this.pickEnabled = false;
  83. // Intentionally not documented.
  84. this.eventType = null;
  85. // Intentionally not documented.
  86. this.clientX = null;
  87. // Intentionally not documented.
  88. this.clientY = null;
  89. // Intentionally not documented.
  90. this.terrainPosition = null;
  91. // Intentionally not documented.
  92. this.latText = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), " ");
  93. this.latText.attributes = new TextAttributes(null);
  94. this.latText.attributes.color = Color.YELLOW;
  95. // Intentionally not documented.
  96. this.lonText = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), " ");
  97. this.lonText.attributes = new TextAttributes(null);
  98. this.lonText.attributes.color = Color.YELLOW;
  99. // Intentionally not documented.
  100. this.elevText = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), " ");
  101. this.elevText.attributes = new TextAttributes(null);
  102. this.elevText.attributes.color = Color.YELLOW;
  103. // Intentionally not documented.
  104. this.eyeText = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), " ");
  105. this.eyeText.attributes = new TextAttributes(null);
  106. this.eyeText.attributes.color = Color.YELLOW;
  107. // Intentionally not documented.
  108. var imageOffset = new Offset(WorldWind.OFFSET_FRACTION, 0.5, WorldWind.OFFSET_FRACTION, 0.5),
  109. imagePath = WorldWind.configuration.baseUrl + "images/crosshair.png";
  110. this.crosshairImage = new ScreenImage(imageOffset, imagePath);
  111. // Register user input event listeners on the WorldWindow.
  112. var thisLayer = this;
  113. function eventListener(event) {
  114. thisLayer.handleUIEvent(event);
  115. }
  116. if (window.PointerEvent) {
  117. worldWindow.addEventListener("pointerdown", eventListener);
  118. worldWindow.addEventListener("pointermove", eventListener);
  119. worldWindow.addEventListener("pointerleave", eventListener);
  120. } else {
  121. worldWindow.addEventListener("mousedown", eventListener);
  122. worldWindow.addEventListener("mousemove", eventListener);
  123. worldWindow.addEventListener("mouseleave", eventListener);
  124. worldWindow.addEventListener("touchstart", eventListener);
  125. worldWindow.addEventListener("touchmove", eventListener);
  126. }
  127. // Register a redraw callback on the WorldWindow.
  128. function redrawCallback(worldWindow, stage) {
  129. thisLayer.handleRedraw(stage);
  130. }
  131. this.wwd.redrawCallbacks.push(redrawCallback);
  132. };
  133. CoordinatesDisplayLayer.prototype = Object.create(Layer.prototype);
  134. // Documented in superclass.
  135. CoordinatesDisplayLayer.prototype.doRender = function (dc) {
  136. var terrainPos = this.terrainPosition,
  137. eyePos = dc.eyePosition,
  138. canvasWidth = dc.currentGlContext.canvas.clientWidth,
  139. x, y, yUnitsScreen, yUnitsText, hideEyeAlt;
  140. if (canvasWidth > 650) { // large canvas, align the text with bottom center
  141. x = (canvasWidth / 2) - 50;
  142. y = 11;
  143. yUnitsScreen = WorldWind.OFFSET_PIXELS;
  144. yUnitsText = 0;
  145. } else if (canvasWidth > 400) { // medium canvas, align the text in the top left
  146. x = 60;
  147. y = 5;
  148. yUnitsScreen = WorldWind.OFFSET_INSET_PIXELS;
  149. yUnitsText = 1;
  150. } else { // small canvas, suppress the eye altitude, align the text in the top left and suppress eye alt
  151. x = 60;
  152. y = 5;
  153. yUnitsScreen = WorldWind.OFFSET_INSET_PIXELS;
  154. yUnitsText = 1;
  155. hideEyeAlt = true;
  156. }
  157. // TODO can we control terrain position visibility with Text's targetVisibility?
  158. this.latText.text = terrainPos ? this.formatLatitude(terrainPos.latitude) : null;
  159. this.latText.screenOffset = new Offset(WorldWind.OFFSET_PIXELS, x, yUnitsScreen, y);
  160. this.latText.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, yUnitsText);
  161. this.latText.render(dc);
  162. x += 70;
  163. this.lonText.text = terrainPos ? this.formatLongitude(terrainPos.longitude) : null;
  164. this.lonText.screenOffset = new Offset(WorldWind.OFFSET_PIXELS, x, yUnitsScreen, y);
  165. this.lonText.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, yUnitsText);
  166. this.lonText.render(dc);
  167. if (!dc.globe.is2D()) {
  168. x += 70;
  169. this.elevText.text = terrainPos ? this.formatAltitude(terrainPos.altitude, "m") : null;
  170. this.elevText.screenOffset = new Offset(WorldWind.OFFSET_PIXELS, x, yUnitsScreen, y);
  171. this.elevText.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, yUnitsText);
  172. this.elevText.render(dc);
  173. }
  174. // TODO can we control eye altitude visibility with Text's targetVisibility?
  175. if (!hideEyeAlt) {
  176. x += 40;
  177. this.eyeText.text = "Eye " + this.formatAltitude(eyePos.altitude, eyePos.altitude < 1000 ? "m" : "km");
  178. this.eyeText.screenOffset = new Offset(WorldWind.OFFSET_PIXELS, x, yUnitsScreen, y);
  179. this.eyeText.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 0, WorldWind.OFFSET_FRACTION, yUnitsText);
  180. this.eyeText.render(dc);
  181. }
  182. // TODO can we control crosshair visibility by adding targetVisibility to ScreenImage?
  183. if (this.eventType === "touch") {
  184. this.crosshairImage.render(dc);
  185. }
  186. this.inCurrentFrame = true;
  187. };
  188. // Intentionally not documented.
  189. CoordinatesDisplayLayer.prototype.handleUIEvent = function (event) {
  190. if (event.type.indexOf("pointer") !== -1) {
  191. this.eventType = event.pointerType; // possible values are "mouse", "pen" and "touch"
  192. } else if (event.type.indexOf("mouse") !== -1) {
  193. this.eventType = "mouse";
  194. } else if (event.type.indexOf("touch") !== -1) {
  195. this.eventType = "touch";
  196. }
  197. if (event.type.indexOf("leave") !== -1) {
  198. this.clientX = null; // clear the event coordinates when a pointer leaves the canvas
  199. this.clientY = null;
  200. } else {
  201. this.clientX = event.clientX;
  202. this.clientY = event.clientY;
  203. }
  204. this.wwd.redraw();
  205. };
  206. // Intentionally not documented.
  207. CoordinatesDisplayLayer.prototype.handleRedraw = function (stage) {
  208. if (stage !== WorldWind.BEFORE_REDRAW) {
  209. return; // ignore after redraw events
  210. }
  211. var pickPoint,
  212. terrainObject;
  213. if ((this.eventType === "mouse" || this.eventType === "pen") && this.clientX && this.clientY) {
  214. pickPoint = this.wwd.canvasCoordinates(this.clientX, this.clientY);
  215. if (pickPoint[0] >= 0 && pickPoint[0] < this.wwd.canvas.width &&
  216. pickPoint[1] >= 0 && pickPoint[1] < this.wwd.canvas.height) {
  217. terrainObject = this.wwd.pickTerrain(pickPoint).terrainObject();
  218. }
  219. } else if (this.eventType === "touch") {
  220. pickPoint = new Vec2(this.wwd.canvas.width / 2, this.wwd.canvas.height / 2);
  221. terrainObject = this.wwd.pickTerrain(pickPoint).terrainObject();
  222. }
  223. this.terrainPosition = terrainObject ? terrainObject.position : null;
  224. };
  225. // Intentionally not documented.
  226. CoordinatesDisplayLayer.prototype.formatLatitude = function (number) {
  227. var suffix = number < 0 ? "\u00b0S" : "\u00b0N";
  228. return Math.abs(number).toFixed(2) + suffix;
  229. };
  230. // Intentionally not documented.
  231. CoordinatesDisplayLayer.prototype.formatLongitude = function (number) {
  232. var suffix = number < 0 ? "\u00b0W" : "\u00b0E";
  233. return Math.abs(number).toFixed(2) + suffix;
  234. };
  235. // Intentionally not documented.
  236. CoordinatesDisplayLayer.prototype.formatAltitude = function (number, units) {
  237. // Convert from meters to the desired units format.
  238. if (units === "km") {
  239. number /= 1e3;
  240. }
  241. // Round to the nearest integer and place a comma every three digits. See the following Stack Overflow
  242. // thread for more information:
  243. // https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript
  244. return number.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " " + units;
  245. };
  246. return CoordinatesDisplayLayer;
  247. });