LiveAtlas/src/leaflet/tileLayer/DynmapTileLayer.ts

290 lines
7.7 KiB
TypeScript
Raw Normal View History

2020-12-16 16:54:41 +00:00
/*
* Copyright 2020 James Lyne
*
* Some portions of this file were taken from https://github.com/webbukkit/dynmap.
* These portions are Copyright 2020 Dynmap 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.
*/
2020-12-12 22:04:56 +00:00
import {TileLayer, Coords, DoneCallback, TileLayerOptions, DomUtil, Util, LatLng} from 'leaflet';
import {DynmapProjection} from "@/leaflet/projection/DynmapProjection";
2020-12-11 21:38:50 +00:00
import {Coordinate, DynmapWorldMap} from "@/dynmap";
2021-05-17 02:39:25 +00:00
import {store} from "@/store";
export interface DynmapTileLayerOptions extends TileLayerOptions {
2020-12-11 21:38:50 +00:00
mapSettings: DynmapWorldMap;
errorTileUrl: string;
2021-02-01 00:16:13 +00:00
night?: boolean;
}
2020-12-12 22:04:56 +00:00
export interface DynmapTileLayer extends TileLayer {
options: DynmapTileLayerOptions;
2020-12-01 23:20:38 +00:00
_projection: DynmapProjection;
2020-12-11 21:38:50 +00:00
_mapSettings: DynmapWorldMap;
2020-12-01 23:20:38 +00:00
_cachedTileUrls: Map<string, string>;
_namedTiles: Map<string, DynmapTileElement>;
_tileTemplate: DynmapTileElement;
_loadQueue: DynmapTileElement[];
_loadingTiles: Set<DynmapTileElement>;
2020-12-12 22:04:56 +00:00
locationToLatLng(location: Coordinate): LatLng;
2020-12-12 22:04:56 +00:00
latLngToLocation(latLng: LatLng): Coordinate;
}
2020-12-01 23:20:38 +00:00
export interface DynmapTile {
active?: boolean;
coords: Coords;
current: boolean;
el: DynmapTileElement;
loaded?: Date;
retain?: boolean;
complete: boolean;
}
export interface DynmapTileElement extends HTMLImageElement {
tileName: string;
}
export interface TileInfo {
prefix: string;
nightday: string;
scaledx: number;
scaledy: number;
zoom: string;
zoomprefix: string;
x: number;
y: number;
fmt: string;
}
// noinspection JSUnusedGlobalSymbols
2020-12-12 22:04:56 +00:00
export class DynmapTileLayer extends TileLayer {
constructor(options: DynmapTileLayerOptions) {
super('', options);
this._mapSettings = options.mapSettings;
options.maxZoom = this._mapSettings.nativeZoomLevels + this._mapSettings.extraZoomLevels;
options.maxNativeZoom = this._mapSettings.nativeZoomLevels;
options.zoomReverse = true;
options.tileSize = 128;
options.minZoom = 0;
2020-12-12 22:04:56 +00:00
Util.setOptions(this, options);
if (options.mapSettings === null) {
throw new TypeError("mapSettings missing");
}
this._projection = new DynmapProjection({
mapToWorld: this._mapSettings.mapToWorld,
worldToMap: this._mapSettings.worldToMap,
nativeZoomLevels: this._mapSettings.nativeZoomLevels,
});
2020-12-01 23:20:38 +00:00
this._cachedTileUrls = Object.seal(new Map());
this._namedTiles = Object.seal(new Map());
this._loadQueue = [];
this._loadingTiles = Object.seal(new Set());
2020-12-12 22:04:56 +00:00
this._tileTemplate = DomUtil.create('img', 'leaflet-tile') as DynmapTileElement;
2020-12-01 23:20:38 +00:00
this._tileTemplate.style.width = this._tileTemplate.style.height = this.options.tileSize + 'px';
this._tileTemplate.alt = '';
this._tileTemplate.tileName = '';
this._tileTemplate.setAttribute('role', 'presentation');
Object.seal(this._tileTemplate);
if(this.options.crossOrigin || this.options.crossOrigin === '') {
this._tileTemplate.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
}
}
getTileName(coords: Coordinate) {
const info = this.getTileInfo(coords);
// Y is inverted for HD-map.
info.y = -info.y;
info.scaledy = info.y >> 5;
return `${info.prefix}${info.nightday}/${info.scaledx}_${info.scaledy}/${info.zoom}${info.x}_${info.y}.${info.fmt}`;
}
getTileUrl(coords: Coordinate) {
return this.getTileUrlFromName(this.getTileName(coords));
}
getTileUrlFromName(name: string, timestamp?: number) {
let url = this._cachedTileUrls.get(name);
if (!url) {
const path = escape(`${this._mapSettings.world.name}/${name}`);
url = `${store.getters.serverConfig.dynmap.tiles}${path}`;
if(typeof timestamp !== 'undefined') {
2020-12-22 18:51:40 +00:00
url += (url.indexOf('?') === -1 ? `?timestamp=${timestamp}` : `&timestamp=${timestamp}`);
}
this._cachedTileUrls.set(name, url);
}
return url;
}
updateNamedTile(name: string, timestamp: number) {
2020-12-01 23:20:38 +00:00
const tile = this._namedTiles.get(name);
this._cachedTileUrls.delete(name);
if (tile) {
tile.dataset.src = this.getTileUrlFromName(name, timestamp);
this._loadQueue.push(tile);
this._tickLoadQueue();
}
}
createTile(coords: Coords, done: DoneCallback) {
2020-12-01 23:20:38 +00:00
//Clone template image instead of creating a new one
const tile = this._tileTemplate.cloneNode(false) as DynmapTileElement;
tile.tileName = this.getTileName(coords);
tile.dataset.src = this.getTileUrl(coords);
this._namedTiles.set(tile.tileName, tile);
this._loadQueue.push(tile);
2020-12-01 23:20:38 +00:00
//Use addEventListener here
tile.onload = () => {
this._tileOnLoad(done, tile);
this._loadingTiles.delete(tile);
this._tickLoadQueue();
};
tile.onerror = () => {
this._tileOnError(done, tile, {name: 'Error', message: 'Error'});
this._loadingTiles.delete(tile);
this._tickLoadQueue();
};
2020-12-01 23:20:38 +00:00
this._tickLoadQueue();
return tile;
}
2020-12-01 23:20:38 +00:00
_tickLoadQueue() {
if (this._loadingTiles.size > 6) {
2020-12-01 23:20:38 +00:00
return;
}
const tile = this._loadQueue.shift();
if (!tile) {
return;
}
this._loadingTiles.add(tile);
tile.src = tile.dataset.src as string;
}
// stops loading all tiles in the background layer
_abortLoading() {
let tile;
2020-12-01 23:20:38 +00:00
for (const i in this._tiles) {
if (!Object.prototype.hasOwnProperty.call(this._tiles, i)) {
continue;
}
2020-12-01 23:20:38 +00:00
tile = this._tiles[i] as DynmapTile;
if (tile.coords.z !== this._tileZoom) {
2020-12-01 23:20:38 +00:00
if (tile.loaded && tile.el && tile.el.tileName) {
this._namedTiles.delete(tile.el.tileName);
}
if(this._loadQueue.includes(tile.el)) {
this._loadQueue.splice(this._loadQueue.indexOf(tile.el), 1);
}
2020-12-01 23:20:38 +00:00
this._loadingTiles.delete(tile.el);
}
}
super._abortLoading.call(this);
}
_removeTile(key: string) {
2020-12-01 23:20:38 +00:00
const tile = this._tiles[key] as DynmapTile;
if (!tile) {
return;
}
2020-12-01 23:20:38 +00:00
const tileName = tile.el.tileName as string;
if (tileName) {
2020-12-01 23:20:38 +00:00
this._namedTiles.delete(tileName);
this._cachedTileUrls.delete(tileName);
this._loadingTiles.delete(tile.el);
if(this._loadQueue.includes(tile.el)) {
this._loadQueue.splice(this._loadQueue.indexOf(tile.el), 1);
}
tile.el.onerror = null;
tile.el.onload = null;
}
// @ts-ignore
super._removeTile.call(this, key);
}
// Some helper functions.
zoomprefix(amount: number) {
// amount == 0 -> ''
// amount == 1 -> 'z_'
// amount == 2 -> 'zz_'
return 'z'.repeat(amount) + (amount === 0 ? '' : '_');
}
getTileInfo(coords: Coordinate): TileInfo {
// zoom: max zoomed in = this.options.maxZoom, max zoomed out = 0
// izoom: max zoomed in = 0, max zoomed out = this.options.maxZoom
// zoomoutlevel: izoom < mapzoomin -> 0, else -> izoom - mapzoomin (which ranges from 0 till mapzoomout)
const izoom = this._getZoomForUrl(),
zoomoutlevel = Math.max(0, izoom - this._mapSettings.extraZoomLevels),
2021-05-27 13:30:56 +00:00
scale = (1 << zoomoutlevel),
x = scale * coords.x,
y = scale * coords.y;
return {
prefix: this._mapSettings.prefix,
2021-02-01 00:16:13 +00:00
nightday: (this._mapSettings.nightAndDay && !this.options.night) ? '_day' : '',
scaledx: x >> 5,
scaledy: y >> 5,
zoom: this.zoomprefix(zoomoutlevel),
zoomprefix: (zoomoutlevel == 0) ? "" : (this.zoomprefix(zoomoutlevel) + "_"),
x: x,
y: y,
fmt: this._mapSettings.imageFormat || 'png'
};
}
getProjection(): DynmapProjection {
return this._projection;
}
2021-02-01 00:16:13 +00:00
setNight(night: boolean) {
if(this.options.night !== night) {
this.options.night = night;
this.redraw();
}
}
}