/*
* 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, "ViewControlsLayer", "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;
});