On moveEnd, when I moved the large canvas and calculated the coordinate of the upper left corner of the tile, everything that is drawn inside the canvas needs to be moved. I need to offset the canvas by the difference between the old position of the upper left corner and the new one.
My code:
import L from 'leaflet';
class CanvasTileLayer extends L.TileLayer {
tileSize: L.Point;
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D | null;
constructor(urlTemplate: string, options?: L.TileLayerOptions) {
super(urlTemplate, options);
this.tileSize = this.getTileSize();
this.canvas = L.DomUtil.create('canvas', 'leaflet-tile-pane');
this.ctx = this.canvas.getContext('2d');
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
createTile(coords: L.Coords, done: L.DoneCallback): HTMLElement {
const tile = super.createTile(coords, done);
const url = this.getTileUrl(coords);
this.canvasRedraw(tile, url, coords);
return tile;
}
private canvasRedraw(tile: HTMLElement, url: string, coords: L.Coords) {
const map: L.Map = this._map;
const bounds = map.getPixelBounds();
// @ts-ignore
const pos = this._getTilePos(coords);
tile.onload = () => {
// delete the original tile that was created with createTile
this.removeTileElement(tile);
this.ctx?.drawImage(
// @ts-ignore
tile,
// Need to find out the difference in coordinates after onmoveend and then subtract it from pos
pos.x,
pos.y,
this.tileSize.x,
this.tileSize.y
);
};
map.on('moveend', () => {
const newBounds = map.getPixelBounds();
// @ts-ignore
const deltaX = Math.floor(newBounds.min.x - bounds.min.x);
// @ts-ignore
const deltaY = Math.floor(newBounds.min.y - bounds.min.y);
console.log(pos.subtract([deltaX, deltaY]));
L.DomUtil.setPosition(this.canvas, pos.subtract([deltaX, deltaY]));
this.ctx?.drawImage(
// @ts-ignore
tile,
pos.x - deltaX,
pos.y - deltaY,
this.tileSize.x,
this.tileSize.y
);
});
tile.onerror = () => {
console.log(`Failed to load tile: ${url}`);
};
}
removeTileElement(tile: HTMLElement) {
tile.parentNode?.removeChild(tile);
}
onAdd(map: L.Map): this {
super.onAdd(map);
this.getPane()?.appendChild(this.canvas);
return this;
}
}
export default CanvasTileLayer;
I thought the map would move and redraw after the moveend events, but the map moves to the wrong place.
I was able to get the desired result.
import L from "leaflet";
class CanvasTileLayer extends L.TileLayer {
readonly tileSize: L.Point;
readonly canvas: HTMLCanvasElement;
readonly ctx: CanvasRenderingContext2D | null;
constructor(urlTemplate: string, options?: L.TileLayerOptions) {
super(urlTemplate, options);
this.tileSize = this.getTileSize();
this.canvas = L.DomUtil.create("canvas", "leaflet-tile-pane");
this.ctx = this.canvas.getContext("2d", { willReadFrequently: true });
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
createTile(coords: L.Coords, done: L.DoneCallback): HTMLImageElement {
const tile = super.createTile(coords, done) as HTMLImageElement;
tile.crossOrigin = "Anonymous";
const url = this.getTileUrl(coords);
this.canvasRedraw(tile, url, coords);
return tile;
}
private canvasRedraw(tile: HTMLImageElement, url: string, coords: L.Coords) {
// @ts-ignore
const pos = this._getTilePos(coords);
tile.onload = () => {
// delete the original tile that was created with createTile
this.removeTileElement(tile);
this.ctx?.drawImage(
// @ts-ignore
tile,
pos.x,
pos.y,
this.tileSize.x,
this.tileSize.y,
);
};
tile.onerror = () => {
console.log(`Failed to load tile: ${url}`);
};
}
removeTileElement(tile: HTMLElement) {
tile.parentNode?.removeChild(tile);
}
onAdd(map: L.Map): this {
super.onAdd(map);
this.getPane()?.appendChild(this.canvas);
const bounds = map.getPixelBounds();
map.on("moveend", () => {
const newBounds = map.getPixelBounds();
let deltaX: number = 0;
let deltaY: number = 0;
if (newBounds.min && bounds.min) {
deltaX = Math.floor(newBounds.min.x - bounds.min.x);
deltaY = Math.floor(newBounds.min.y - bounds.min.y);
}
L.DomUtil.setPosition(this.canvas, new L.Point(deltaX, deltaY));
const imageData = this.ctx?.getImageData(
0,
0,
this.canvas.width,
this.canvas.height,
);
if (imageData) this.ctx?.putImageData(imageData, -deltaX, -deltaY);
for (const tile of Object.values(this._tiles)) {
// @ts-ignore
const pos = this._getTilePos(tile.coords);
this.ctx?.drawImage(
tile.el as HTMLImageElement,
pos.x - deltaX,
pos.y - deltaY,
this.tileSize.x,
this.tileSize.y,
);
}
});
return this;
}
}
export default CanvasTileLayer;