import * as mercator from "../mercator";
import * as utils from "../utils/shared";
import {createColorRGB} from "../utils/shared";
import {createEvents, Events, EventsHandler} from "../Events";
import {Extent} from "../Extent";
import {LonLat} from "../LonLat";
import {Node} from "../quadTree/Node";
import {Material} from "./Material";
import {Planet} from "../scene/Planet";
import {Segment} from "../segment/Segment";
import {Vec3, NumberArray3} from "../math/Vec3";
import {NumberArray4} from "../math/Vec4";
import {IDefaultTextureParams, WebGLTextureExt} from "../webgl/Handler";
const FADING_RATIO = 30;
export interface ILayerParams {
properties?: any;
labelMaxLetters?: number;
hideInLayerSwitcher?: boolean;
opacity?: number;
minZoom?: number;
maxZoom?: number;
attribution?: string;
zIndex?: number;
isBaseLayer?: boolean;
defaultTextures?: [IDefaultTextureParams, IDefaultTextureParams];
visibility?: boolean;
fading?: boolean;
height?: number;
textureFilter?: string;
isSRGB?: boolean;
pickingEnabled?: boolean;
preLoadZoomLevels?: number[];
extent?: Extent | [[number, number], [number, number]];
ambient?: string | NumberArray3 | Vec3;
diffuse?: string | NumberArray3 | Vec3;
specular?: string | NumberArray3 | Vec3;
shininess?: number;
nightTextureCoefficient?: number;
iconSrc?: string | null;
}
/**
* @class
* Base class; normally only used for creating subclasses and not instantiated in apps.
* A visual representation of raster or vector map data well known as a layer.
* @class
* @param {String} [name="noname"] - Layer name.
* @param {Object} [options] - Layer options:
* @param {number} [options.opacity=1.0] - Layer opacity.
* @param {number} [options.minZoom=0] - Minimal visibility zoom level.
* @param {number} [options.maxZoom=0] - Maximal visibility zoom level.
* @param {string} [options.attribution] - Layer attribution that displayed in the attribution area on the screen.
* @param {boolean} [options.isBaseLayer=false] - This is a base layer.
* @param {boolean} [options.visibility=true] - Layer visibility.
* @param {boolean} [options.hideInLayerSwitcher=false] - Presence of layer in dialog window of LayerSwitcher control.
* @param {boolean} [options.isSRGB=false] - Layer image webgl internal format.
* @param {Extent} [options.extent=[[-180.0, -90.0], [180.0, 90.0]]] - Visible extent.
* @param {string} [options.textureFilter="anisotropic"] - Image texture filter. Available values: "nearest", "linear", "mipmap" and "anisotropic".
* @param {string} [options.icon] - Icon for LayerSwitcher
* @fires EventsHandler<LayerEventsList>#visibilitychange
* @fires EventsHandler<LayerEventsList>#add
* @fires EventsHandler<LayerEventsList>#remove
* @fires EventsHandler<LayerEventsList>#mousemove
* @fires EventsHandler<LayerEventsList>#mouseenter
* @fires EventsHandler<LayerEventsList>#mouseleave
* @fires EventsHandler<LayerEventsList>#lclick
* @fires EventsHandler<LayerEventsList>#rclick
* @fires EventsHandler<LayerEventsList>#mclick
* @fires EventsHandler<LayerEventsList>#ldblclick
* @fires EventsHandler<LayerEventsList>#rdblclick
* @fires EventsHandler<LayerEventsList>#mdblclick
* @fires EventsHandler<LayerEventsList>#lup
* @fires EventsHandler<LayerEventsList>#rup
* @fires EventsHandler<LayerEventsList>#mup
* @fires EventsHandler<LayerEventsList>#ldown
* @fires EventsHandler<LayerEventsList>#rdown
* @fires EventsHandler<LayerEventsList>#mdown
* @fires EventsHandler<LayerEventsList>#lhold
* @fires EventsHandler<LayerEventsList>#rhold
* @fires EventsHandler<LayerEventsList>#mhold
* @fires EventsHandler<LayerEventsList>#mousewheel
* @fires EventsHandler<LayerEventsList>#touchmove
* @fires EventsHandler<LayerEventsList>#touchstart
* @fires EventsHandler<LayerEventsList>#touchend
* @fires EventsHandler<LayerEventsList>#doubletouch
*/
class Layer {
static __counter__: number = 0;
/**
* Uniq identifier.
* @public
* @type {number}
*/
public __id: number;
/**
* Events handler.
* @public
* @type {Events}
*/
public events: EventsHandler<LayerEventsList>;
/**
* Layer user name.
* @public
* @type {string}
*/
public name: string;
public properties: any;
public hideInLayerSwitcher: boolean;
/**
* Minimal zoom level when layer is visible.
* @public
* @type {number}
*/
public minZoom: number;
/**
* Maximal zoom level when layer is visible.
* @public
* @type {number}
*/
public maxZoom: number;
/**
* Planet node.
* @public
* @type {Planet}
*/
public _planet: Planet | null;
public createTexture: Function | null;
public nightTextureCoefficient: number;
protected _hasImageryTiles: boolean;
/**
* Layer global opacity.
* @public
* @type {number}
*/
protected _opacity: number;
/**
* Layer attribution.
* @protected
* @type {string}
*/
protected _attribution: string;
/**
* Layer z-index.
* @protected
* @type {number}
*/
protected _zIndex: number;
/**
* Base layer type flag.
* @protected
* @type {boolean}
*/
protected _isBaseLayer: boolean;
public _defaultTextures: [IDefaultTextureParams | null, IDefaultTextureParams | null];
/**
* Layer visibility.
* @protected
* @type {boolean}
*/
protected _visibility: boolean;
public _fading: boolean;
protected _fadingFactor: number;
public _fadingOpacity: number;
/**
* Height over the ground.
* @public
* @type {number}
*/
public _height: number;
protected _textureFilter: string;
protected _isSRGB: boolean;
public _internalFormat: number | null;
/**
* Visible degrees extent.
* @public
* @type {Extent}
*/
public _extent: Extent;
/**
* Visible mercator extent.
* @public
* @type {Extent}
*/
public _extentMerc: Extent;
/**
* Layer picking color. Assign when added to the planet.
* @public
* @type {Vec3}
*/
public _pickingColor: Vec3;
public _pickingEnabled: boolean;
protected _isPreloadDone: boolean;
protected _preLoadZoomLevels: number[];
public _ambient: Float32Array | null;
public _diffuse: Float32Array | null;
public _specular: Float32Array | null;
public isVector: boolean = false;
protected _iconSrc: string | null;
constructor(name?: string | null, options: ILayerParams = {}) {
this.__id = Layer.__counter__++;
this._iconSrc = options.iconSrc || null;
this.events = createEvents<LayerEventsList>(LAYER_EVENTS, this);
this.name = name || "noname";
this.properties = options.properties || {};
this.hideInLayerSwitcher = options.hideInLayerSwitcher || false;
this._hasImageryTiles = true;
this._opacity = options.opacity != undefined ? options.opacity : 1.0;
this.minZoom = options.minZoom || 0;
this.maxZoom = options.maxZoom != undefined ? options.maxZoom : 50;
this._planet = null;
this.isVector = false;
this._attribution = options.attribution || "";
this._zIndex = options.zIndex || 0;
this._isBaseLayer = options.isBaseLayer || false;
this._defaultTextures = options.defaultTextures || [null, null];
this._visibility = options.visibility !== undefined ? options.visibility : true;
this._fading = options.fading || false;
this._fadingFactor = this._opacity / FADING_RATIO;
if (this._fading) {
this._fadingOpacity = 0;
} else {
this._fadingOpacity = this._opacity;
}
this._height = options.height || 0;
this._extent = new Extent();
this.createTexture = null;
this._textureFilter = options.textureFilter ? options.textureFilter.trim().toUpperCase() : "MIPMAP";
this._isSRGB = options.isSRGB != undefined ? options.isSRGB : false;
this._internalFormat = null;
this._extentMerc = new Extent();
// Setting the extent up
this.setExtent(
utils.createExtent(
options.extent,
new Extent(new LonLat(-180, -90), new LonLat(180, 90))
)
);
/**
* Layer picking color. Assign when added to the planet.
* @protected
* @type {Vec3}
*/
this._pickingColor = new Vec3();
this._pickingEnabled = options.pickingEnabled !== undefined ? options.pickingEnabled : true;
this._isPreloadDone = false;
this._preLoadZoomLevels = options.preLoadZoomLevels || [0, 1];
this._ambient = null;
this._diffuse = null;
this._specular = null;
if (options.ambient) {
let a = utils.createColorRGB(options.ambient, new Vec3(0.2, 0.2, 0.2));
this._ambient = new Float32Array([a.x, a.y, a.z]);
}
if (options.diffuse) {
let d = utils.createColorRGB(options.diffuse, new Vec3(0.8, 0.8, 0.8));
this._diffuse = new Float32Array([d.x, d.y, d.z]);
}
if (options.specular) {
let s = utils.createColorRGB(options.specular, new Vec3(0.0003, 0.0003, 0.0003));
let shininess = options.shininess || 20.0;
this._specular = new Float32Array([s.x, s.y, s.z, shininess]);
}
this.nightTextureCoefficient = options.nightTextureCoefficient || 1.0;
}
public get iconSrc(): string | null {
return this._iconSrc;
}
public set iconSrc(src: string | null) {
// @todo: add event
this._iconSrc = src;
}
public set diffuse(rgb: string | NumberArray3 | Vec3 | null | undefined) {
if (rgb) {
let vec = createColorRGB(rgb);
this._diffuse = new Float32Array(vec.toArray());
} else {
this._diffuse = null;
}
}
public set ambient(rgb: string | NumberArray3 | Vec3 | null | undefined) {
if (rgb) {
let vec = createColorRGB(rgb);
this._ambient = new Float32Array(vec.toArray());
} else {
this._ambient = null;
}
}
public set specular(rgb: string | NumberArray3 | Vec3 | null | undefined) {
if (rgb) {
let vec = createColorRGB(rgb);
this._specular = new Float32Array([vec.x, vec.y, vec.y, this._specular ? this._specular[3] : 0.0]);
} else {
this._specular = null;
}
}
public set shininess(v: number) {
if (this._specular) {
this._specular[3] = v;
}
}
// get normalMapCreator() {
// return this._normalMapCreator;
// }
static getTMS(x: number, y: number, z: number): { x: number; y: number; z: number } {
return {
x: x,
y: (1 << z) - y - 1,
z: z
};
}
static getTileIndex(x: number, y: number, z: number, tileGroup: number): string {
return `${tileGroup}::${x}_${y}_${z}`;
}
public get instanceName(): string {
return "Layer";
}
public get rendererEvents(): EventsHandler<LayerEventsList> {
return this.events;
}
public set opacity(opacity: number) {
if (opacity !== this._opacity) {
if (this._fading) {
if (opacity > this._opacity) {
this._fadingFactor = (opacity - this._opacity) / FADING_RATIO;
} else if (opacity < this._opacity) {
this._fadingFactor = (this._opacity - opacity) / FADING_RATIO;
}
} else {
this._fadingOpacity = opacity;
}
this._opacity = opacity;
}
}
public set pickingEnabled(picking: boolean) {
this._pickingEnabled = picking;
}
public get pickingEnabled(): boolean {
return this._pickingEnabled;
}
/**
* Returns true if a layer has imagery tiles.
* @public
* @virtual
* @returns {boolean} - Imagery tiles flag.
*/
public hasImageryTiles(): boolean {
return this._hasImageryTiles;
}
/**
* Gets layer identifier.
* @public
* @returns {string} - Layer object id.
*/
public getID(): number {
return this.__id;
}
public get id(): number {
return this.__id;
}
/**
* @todo: remove after all
*/
public get _id(): number {
return this.__id;
}
/**
* Compares layers instances.
* @public
* @param {Layer} layer - Layer instance to compare.
* @returns {boolean} - Returns true if the layers is the same instance of the input.
*/
public isEqual(layer: Layer): boolean {
return layer.__id === this.__id;
}
/**
* Assign the planet.
* @protected
* @virtual
* @param {Planet} planet - Planet render node.
*/
public _assignPlanet(planet: Planet) {
this._planet = planet;
planet._layers.push(this);
if (planet.renderer && planet.renderer.isInitialized()) {
// TODO: webgl1
if (this._isSRGB) {
this._internalFormat = planet.renderer.handler.gl!.SRGB8_ALPHA8;
} else {
this._internalFormat = planet.renderer.handler.gl!.RGBA8;
}
this.createTexture = planet.renderer.handler.createTexture[this._textureFilter];
this.events.on("visibilitychange", planet._onLayerVisibilityChanged, planet);
if (this._isBaseLayer && this._visibility) {
planet.setBaseLayer(this);
}
planet.events.dispatch(planet.events.layeradd, this);
this.events.dispatch(this.events.add, planet);
planet.updateVisibleLayers();
this._bindPicking();
if (this._visibility && this.hasImageryTiles()) {
this._preLoad();
}
}
}
public get isIdle(): boolean {
return this._planet && this._planet._terrainCompletedActivated || false;
}
/**
* Assign picking color to the layer.
* @protected
* @virtual
*/
protected _bindPicking() {
this._planet && this._planet.renderer && this._planet.renderer.assignPickingColor<Layer>(this);
}
/**
* Adds layer to the planet.
* @public
* @param {Planet} planet - Adds layer to the planet.
*/
public addTo(planet: Planet) {
if (!this._planet) {
this._assignPlanet(planet);
}
}
/**
* Removes from planet.
* @public
* @returns {Layer} -This layer.
*/
public remove(): this {
let p = this._planet;
if (p) {
//TODO: replace to planet
for (let i = 0; i < p._layers.length; i++) {
if (this.isEqual(p._layers[i])) {
p.renderer && p.renderer.clearPickingColor(this);
p._layers.splice(i, 1);
p.updateVisibleLayers();
this.clear();
p.events.dispatch(p.events.layerremove, this);
this.events.dispatch(this.events.remove, p);
this._planet = null;
this._internalFormat = null;
this.createTexture = null;
return this;
}
}
}
return this;
}
/**
* Clears layer material.
* @virtual
*/
public clear() {
if (this._planet) {
this._planet._clearLayerMaterial(this);
}
}
/**
* Returns planet instance.
*/
get planet(): Planet | null {
return this._planet;
}
/**
* Sets layer attribution text.
* @public
* @param {string} html - HTML code that represents layer attribution, it could be just a text.
*/
public setAttribution(html: string) {
if (this._attribution !== html) {
this._attribution = html;
this._planet && this._planet.updateAttributionsList();
}
}
/**
* Gets layer attribution.
* @public
* @returns {string} Layer attribution
*/
public getAttribution(): string {
return this._attribution;
}
/**
* Sets height over the ground.
* @public
* @param {number} height - Layer height.
*/
public setHeight(height: number) {
this._height = height;
this._planet && this._planet.updateVisibleLayers();
}
/**
* Gets layer height.
* @public
* @returns {number} -
*/
public getHeight(): number {
return this._height;
}
/**
* Sets z-index.
* @public
* @param {number} zIndex - Layer z-index.
*/
public setZIndex(zIndex: number) {
this._zIndex = zIndex;
this._planet && this._planet.updateVisibleLayers();
}
/**
* Gets z-index.
* @public
* @returns {number} -
*/
public getZIndex(): number {
return this._zIndex;
}
/**
* Set zIndex to the maximal value depend on other layers on the planet.
* @public
*/
public bringToFront() {
if (this._planet) {
let vl = this._planet.visibleTileLayers;
let l = vl[vl.length - 1];
if (!l.isEqual(this)) {
this.setZIndex(l.getZIndex() + 1);
}
}
}
/**
* Returns true if the layer is a base.
* @public
* @returns {boolean} - Base layer flag.
*/
public isBaseLayer(): boolean {
return this._isBaseLayer;
}
/**
* Sets base layer type true.
* @public
* @param {boolean} isBaseLayer -
*/
public setBaseLayer(isBaseLayer: boolean) {
this._isBaseLayer = isBaseLayer;
if (this._planet) {
if (!isBaseLayer && this._planet.baseLayer && this.isEqual(this._planet.baseLayer)) {
this._planet.baseLayer = null;
}
this._planet.updateVisibleLayers();
}
}
/**
* Sets layer visibility.
* @public
* @virtual
* @param {boolean} visibility - Layer visibility.
*/
public setVisibility(visibility: boolean) {
if (visibility !== this._visibility) {
this._visibility = visibility;
if (this._planet) {
if (this._isBaseLayer && visibility) {
this._planet.setBaseLayer(this);
}
this._planet.updateVisibleLayers();
if (visibility && !this._isPreloadDone && !this.isVector) {
this._isPreloadDone = true;
this._preLoad();
}
}
this.events.dispatch(this.events.visibilitychange, this);
}
}
protected _forceMaterialApply(segment: Segment) {
let pm = segment.materials,
m = pm[this.__id];
if (!m) {
m = pm[this.__id] = this.createMaterial(segment);
}
if (!m.isReady) {
this._planet!._renderCompleted = false;
}
this.applyMaterial(m, true);
}
public clearMaterial(material: Material) {
//empty
}
public loadMaterial(material: Material, forceLoading: boolean = false) {
//empty
}
public applyMaterial(m: Material, isForced: boolean = false): NumberArray4 {
return [0, 0, 1, 1];
}
protected _preLoadRecursive(node: Node, maxZoom: number) {
if (node.segment.tileZoom > maxZoom) {
return;
}
if (this._preLoadZoomLevels.includes(node.segment.tileZoom)) {
this._forceMaterialApply(node.segment);
}
for (let i = 0, len = node.nodes.length; i < len; i++) {
if (node.nodes[i]) {
this._preLoadRecursive(node.nodes[i], maxZoom);
}
}
}
protected _preLoad() {
if (this._planet && this._preLoadZoomLevels.length) {
let p = this._planet,
maxZoom = Math.max(...this._preLoadZoomLevels);
for (let i = 0, len = p.quadTreeStrategy.quadTreeList.length; i < len; i++) {
this._preLoadRecursive(p.quadTreeStrategy.quadTreeList[i], maxZoom);
}
}
}
/**
* Gets layer visibility.
* @public
* @returns {boolean} - Layer visibility.
*/
public getVisibility(): boolean {
return this._visibility;
}
/**
* Sets visible geographical extent.
* @public
* @param {Extent} extent - Layer visible geographical extent.
*/
public setExtent(extent: Extent) {
let sw = extent.southWest.clone(),
ne = extent.northEast.clone();
if (sw.lat < mercator.MIN_LAT) {
sw.lat = mercator.MIN_LAT;
}
if (ne.lat > mercator.MAX_LAT) {
ne.lat = mercator.MAX_LAT;
}
this._extent = extent.clone();
this._extentMerc = new Extent(sw.forwardMercator(), ne.forwardMercator());
this._correctFullExtent();
}
/**
* Gets layer extent.
* @public
* @return {Extent} - Layer geodetic extent.
*/
public getExtent(): Extent {
return this._extent;
}
/**
* Gets layer web-mercator extent.
* @public
* @return {Extent} - Layer extent.
*/
public getExtentMerc(): Extent {
return this._extentMerc;
}
/**
* Fly extent.
* @public
*/
public flyExtent() {
this._planet?.flyExtent(this.getExtent());
}
/**
* View extent.
* @public
*/
public viewExtent() {
this._planet?.viewExtent(this.getExtent());
}
/**
* Special correction of the whole globe extent.
* @protected
*/
protected _correctFullExtent() {
// var e = this._extent,
// em = this._extentMerc;
// var ENLARGE_MERCATOR_LON = og.mercator.POLE + 50000;
// var ENLARGE_MERCATOR_LAT = og.mercator.POLE + 50000;
// if (e.northEast.lat === 90.0) {
// em.northEast.lat = ENLARGE_MERCATOR_LAT;
// }
// if (e.northEast.lon === 180.0) {
// em.northEast.lon = ENLARGE_MERCATOR_LON;
// }
// if (e.southWest.lat === -90.0) {
// em.southWest.lat = -ENLARGE_MERCATOR_LAT;
// }
// if (e.southWest.lon === -180.0) {
// em.southWest.lon = -ENLARGE_MERCATOR_LON;
// }
}
public get opacity(): number {
return this._opacity;
}
public get screenOpacity(): number {
return this._fading ? this._fadingOpacity : this._opacity;
}
public _refreshFadingOpacity() {
let p = this._planet!;
if (
this._visibility && p.getViewExtent().overlaps(this._extent) &&
p.maxCurrZoom >= this.minZoom &&
p.minCurrZoom <= this.maxZoom
) {
this._fadingOpacity += this._fadingFactor;
if (
(this._fadingFactor > 0.0 && this._fadingOpacity > this._opacity) ||
(this._fadingFactor < 0.0 && this._fadingOpacity < this._opacity)
) {
this._fadingOpacity = this._opacity;
}
return false;
} else {
this._fadingOpacity -= this._fadingFactor;
if (this._fadingOpacity <= 0) {
this._fadingOpacity = 0.0;
}
return false;
}
}
public createMaterial(segment: Segment): Material {
return new Material(segment, this);
}
public redraw() {
if (this._planet) {
this._planet.quadTreeStrategy.clearLayerMaterial(this);
// this._planet._quadTree.traverseTree((n: Node) => {
// if (n.segment.materials[this.__id]) {
// n.segment.materials[this.__id].clear();
// }
// }
// );
//
// this._planet._quadTreeNorth.traverseTree((n: Node) => {
// if (n.segment.materials[this.__id]) {
// n.segment.materials[this.__id].clear();
// }
// }
// );
//
// this._planet._quadTreeSouth.traverseTree((n: Node) => {
// if (n.segment.materials[this.__id]) {
// n.segment.materials[this.__id].clear();
// }
// }
// );
}
}
public abortMaterialLoading(material: Material) {
}
public abortLoading() {
}
}
export type LayerEventsList = [
"visibilitychange",
"add",
"remove",
"mousemove",
"mouseenter",
"mouseleave",
"lclick",
"rclick",
"mclick",
"ldblclick",
"rdblclick",
"mdblclick",
"lup",
"rup",
"mup",
"ldown",
"rdown",
"mdown",
"lhold",
"rhold",
"mhold",
"mousewheel",
"touchmove",
"touchstart",
"touchend",
"doubletouch",
"touchleave",
"touchenter"
];
export const LAYER_EVENTS: LayerEventsList = [
/**
* Triggered when layer visibility changed.
* @event og.Layer#visibilitychange
*/
"visibilitychange",
/**
* Triggered when layer has added to the planet.
* @event og.Layer#add
*/
"add",
/**
* Triggered when layer has removed from the planet.
* @event og.Layer#remove
*/
"remove",
/**
* Triggered when mouse moves over the layer.
* @event og.Layer#mousemove
*/
"mousemove",
/**
* Triggered when mouse has entered over the layer.
* @event og.Layer#mouseenter
*/
"mouseenter",
/**
* Triggered when mouse leaves the layer.
* @event og.Layer#mouseenter
*/
"mouseleave",
/**
* Mouse left button clicked.
* @event og.Layer#lclick
*/
"lclick",
/**
* Mouse right button clicked.
* @event og.Layer#rclick
*/
"rclick",
/**
* Mouse right button clicked.
* @event og.Layer#mclick
*/
"mclick",
/**
* Mouse left button double click.
* @event og.Layer#ldblclick
*/
"ldblclick",
/**
* Mouse right button double click.
* @event og.Layer#rdblclick
*/
"rdblclick",
/**
* Mouse middle button double click.
* @event og.Layer#mdblclick
*/
"mdblclick",
/**
* Mouse left button up(stop pressing).
* @event og.Layer#lup
*/
"lup",
/**
* Mouse right button up(stop pressing).
* @event og.Layer#rup
*/
"rup",
/**
* Mouse middle button up(stop pressing).
* @event og.Layer#mup
*/
"mup",
/**
* Mouse left button is just pressed down(start pressing).
* @event og.Layer#ldown
*/
"ldown",
/**
* Mouse right button is just pressed down(start pressing).
* @event og.Layer#rdown
*/
"rdown",
/**
* Mouse middle button is just pressed down(start pressing).
* @event og.Layer#mdown
*/
"mdown",
/**
* Mouse left button is pressing.
* @event og.Layer#lhold
*/
"lhold",
/**
* Mouse right button is pressing.
* @event og.Layer#rhold
*/
"rhold",
/**
* Mouse middle button is pressing.
* @event og.Layer#mhold
*/
"mhold",
/**
* Mouse wheel is rotated.
* @event og.Layer#mousewheel
*/
"mousewheel",
/**
* Triggered when touching moves over the layer.
* @event og.Layer#touchmove
*/
"touchmove",
/**
* Triggered when layer begins to touch.
* @event og.Layer#touchstart
*/
"touchstart",
/**
* Triggered when layer has finished touching.
* @event og.Layer#touchend
*/
"touchend",
/**
* Triggered layer has double touched.
* @event og.Layer#doubletouch
*/
"doubletouch",
/**
* Triggered when touching leaves layer borders.
* @event og.Layer#touchleave
*/
"touchleave",
/**
* Triggered when touch enters over the layer.
* @event og.Layer#touchenter
*/
"touchenter"
];
export {Layer};