text: /*
   * Copyright 2015-2017 WorldWind Contributors
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  /**
   * @exports CoordinatesDisplayLayer
   */
  define([
          '../error/ArgumentError',
          '../util/Color',
          '../util/Font',
          '../layer/Layer',
          '../util/Logger',
          '../util/Offset',
          '../geom/Position',
          '../shapes/ScreenImage',
          '../shapes/ScreenText',
          '../shapes/TextAttributes',
          '../geom/Vec2'
      ],
      function (ArgumentError,
                Color,
                Font,
                Layer,
                Logger,
                Offset,
                Position,
                ScreenImage,
                ScreenText,
                TextAttributes,
                Vec2) {
          "use strict";
  
          /**
           * Constructs a layer that displays the current map coordinates.
           * @alias CoordinatesDisplayLayer
           * @constructor
           * @augments Layer
           * @classDesc Displays the current map coordinates. A coordinates display layer cannot be shared among World
           * Windows. Each WorldWindow if it is to have a coordinates display layer must have its own. See the
           * MultiWindow example for guidance.
           * @param {WorldWindow} worldWindow The WorldWindow associated with this layer.
           * This layer may not be associated with more than one WorldWindow. Each WorldWindow must have it's own
           * instance of this layer if each window is to have a coordinates display.
           * @throws {ArgumentError} If the specified WorldWindow is null or undefined.
           */
          var CoordinatesDisplayLayer = function (worldWindow) {
              if (!worldWindow) {
                  throw new ArgumentError(
                      Logger.logMessage(Logger.LEVEL_SEVERE, "CoordinatesDisplayLayer", "constructor", "missingWorldWindow"));
              }
  
              Layer.call(this, "Coordinates");
  
              /**
               * The WorldWindow associated with this layer.
               * @type {WorldWindow}
               * @readonly
               */
              this.wwd = worldWindow;
  
              // No picking of this layer's screen elements.
              this.pickEnabled = false;
  
              // Intentionally not documented.
              this.eventType = null;
  
              // Intentionally not documented.
              this.clientX = null;
  
              // Intentionally not documented.
              this.clientY = null;
  
              // Intentionally not documented.
              this.terrainPosition = null;
  
              // Intentionally not documented.
              this.latText = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), " ");
              this.latText.attributes = new TextAttributes(null);
              this.latText.attributes.color = Color.YELLOW;
  
              // Intentionally not documented.
              this.lonText = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), " ");
              this.lonText.attributes = new TextAttributes(null);
              this.lonText.attributes.color = Color.YELLOW;
  
              // Intentionally not documented.
              this.elevText = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), " ");
              this.elevText.attributes = new TextAttributes(null);
              this.elevText.attributes.color = Color.YELLOW;
  
              // Intentionally not documented.
              this.eyeText = new ScreenText(new Offset(WorldWind.OFFSET_PIXELS, 0, WorldWind.OFFSET_PIXELS, 0), " ");
              this.eyeText.attributes = new TextAttributes(null);
              this.eyeText.attributes.color = Color.YELLOW;
  
              // Intentionally not documented.
              var imageOffset = new Offset(WorldWind.OFFSET_FRACTION, 0.5, WorldWind.OFFSET_FRACTION, 0.5),
                  imagePath = WorldWind.configuration.baseUrl + "images/crosshair.png";
              this.crosshairImage = new ScreenImage(imageOffset, imagePath);
  
              // Register user input event listeners on the WorldWindow.
              var thisLayer = this;
  
              function eventListener(event) {
                  thisLayer.handleUIEvent(event);
              }
  
              if (window.PointerEvent) {
                  worldWindow.addEventListener("pointerdown", eventListener);
                  worldWindow.addEventListener("pointermove", eventListener);
                  worldWindow.addEventListener("pointerleave", eventListener);
              } else {
                  worldWindow.addEventListener("mousedown", eventListener);
                  worldWindow.addEventListener("mousemove", eventListener);
                  worldWindow.addEventListener("mouseleave", eventListener);
                  worldWindow.addEventListener("touchstart", eventListener);
                  worldWindow.addEventListener("touchmove", eventListener);
              }
  
              // Register a redraw callback on the WorldWindow.
              function redrawCallback(worldWindow, stage) {
                  thisLayer.handleRedraw(stage);
              }
  
              this.wwd.redrawCallbacks.push(redrawCallback);
          };
  
          CoordinatesDisplayLayer.prototype = Object.create(Layer.prototype);
  
          // Documented in superclass.
          CoordinatesDisplayLayer.prototype.doRender = function (dc) {
              var terrainPos = this.terrainPosition,
                  eyePos = dc.eyePosition,
                  canvasWidth = dc.currentGlContext.canvas.clientWidth,
                  x, y, yUnitsScreen, yUnitsText, hideEyeAlt;
  
              if (canvasWidth > 650) { // large canvas, align the text with bottom center
                  x = (canvasWidth / 2) - 50;
                  y = 5;
                  yUnitsScreen = WorldWind.OFFSET_PIXELS;
                  yUnitsText = 0;
              } else if (canvasWidth > 400) { // medium canvas, align the text in the top left
                  x = 60;
                  y = 5;
                  yUnitsScreen = WorldWind.OFFSET_INSET_PIXELS;
                  yUnitsText = 1;
              } else { // small canvas, suppress the eye altitude, align the text in the top left and suppress eye alt
                  x = 60;
                  y = 5;
                  yUnitsScreen = WorldWind.OFFSET_INSET_PIXELS;
                  yUnitsText = 1;
                  hideEyeAlt = true;
              }
  
              // TODO can we control terrain position visibility with Text's targetVisibility?
              this.latText.text = terrainPos ? this.formatLatitude(terrainPos.latitude) : null;
              this.latText.screenOffset = new Offset(WorldWind.OFFSET_PIXELS, x, yUnitsScreen, y);
              this.latText.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, yUnitsText);
              this.latText.render(dc);
  
              x += 70;
              this.lonText.text = terrainPos ? this.formatLongitude(terrainPos.longitude) : null;
              this.lonText.screenOffset = new Offset(WorldWind.OFFSET_PIXELS, x, yUnitsScreen, y);
              this.lonText.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, yUnitsText);
              this.lonText.render(dc);
  
              if (!dc.globe.is2D()) {
                  x += 70;
                  this.elevText.text = terrainPos ? this.formatAltitude(terrainPos.altitude, "m") : null;
                  this.elevText.screenOffset = new Offset(WorldWind.OFFSET_PIXELS, x, yUnitsScreen, y);
                  this.elevText.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 1, WorldWind.OFFSET_FRACTION, yUnitsText);
                  this.elevText.render(dc);
              }
  
              // TODO can we control eye altitude visibility with Text's targetVisibility?
              if (!hideEyeAlt) {
                  x += 40;
                  this.eyeText.text = "Eye  " + this.formatAltitude(eyePos.altitude, eyePos.altitude < 1000 ? "m" : "km");
                  this.eyeText.screenOffset = new Offset(WorldWind.OFFSET_PIXELS, x, yUnitsScreen, y);
                  this.eyeText.attributes.offset = new Offset(WorldWind.OFFSET_FRACTION, 0, WorldWind.OFFSET_FRACTION, yUnitsText);
                  this.eyeText.render(dc);
              }
  
              // TODO can we control crosshair visibility by adding targetVisibility to ScreenImage?
              if (this.eventType == "touch") {
                  this.crosshairImage.render(dc);
              }
  
              this.inCurrentFrame = true;
          };
  
          // Intentionally not documented.
          CoordinatesDisplayLayer.prototype.handleUIEvent = function (event) {
              if (event.type.indexOf("pointer") != -1) {
                  this.eventType = event.pointerType; // possible values are "mouse", "pen" and "touch"
              } else if (event.type.indexOf("mouse") != -1) {
                  this.eventType = "mouse";
              } else if (event.type.indexOf("touch") != -1) {
                  this.eventType = "touch";
              }
  
              if (event.type.indexOf("leave") != -1) {
                  this.clientX = null; // clear the event coordinates when a pointer leaves the canvas
                  this.clientY = null;
              } else {
                  this.clientX = event.clientX;
                  this.clientY = event.clientY;
              }
  
              this.wwd.redraw();
          };
  
          // Intentionally not documented.
          CoordinatesDisplayLayer.prototype.handleRedraw = function (stage) {
              if (stage != WorldWind.BEFORE_REDRAW) {
                  return; // ignore after redraw events
              }
  
              var pickPoint,
                  terrainObject;
  
              if ((this.eventType == "mouse" || this.eventType == "pen") && this.clientX && this.clientY) {
                  pickPoint = this.wwd.canvasCoordinates(this.clientX, this.clientY);
                  if (pickPoint[0] >= 0 && pickPoint[0] < this.wwd.canvas.width &&
                      pickPoint[1] >= 0 && pickPoint[1] < this.wwd.canvas.height) {
                      terrainObject = this.wwd.pickTerrain(pickPoint).terrainObject();
                  }
              } else if (this.eventType == "touch") {
                  pickPoint = new Vec2(this.wwd.canvas.width / 2, this.wwd.canvas.height / 2);
                  terrainObject = this.wwd.pickTerrain(pickPoint).terrainObject();
              }
  
              this.terrainPosition = terrainObject ? terrainObject.position : null;
          };
  
          // Intentionally not documented.
          CoordinatesDisplayLayer.prototype.formatLatitude = function (number) {
              var suffix = number < 0 ? "\u00b0S" : "\u00b0N";
              return Math.abs(number).toFixed(2) + suffix;
          };
  
          // Intentionally not documented.
          CoordinatesDisplayLayer.prototype.formatLongitude = function (number) {
              var suffix = number < 0 ? "\u00b0W" : "\u00b0E";
              return Math.abs(number).toFixed(2) + suffix;
          };
  
          // Intentionally not documented.
          CoordinatesDisplayLayer.prototype.formatAltitude = function (number, units) {
              // Convert from meters to the desired units format.
              if (units === "km") {
                  number /= 1e3;
              }
  
              // Round to the nearest integer and place a comma every three digits. See the following Stack Overflow
              // thread for more information:
              // https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript
              return number.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ",") + " " + units;
          };
  
          return CoordinatesDisplayLayer;
      });