Pixi 8 support?
Will this project support Pixi 8?
Or is there some example how to make it work with the latest pixi version?
I started porting the library to typescript and using pixijs 8.
there are some breaking changes but nothing that cant be fixed
isWebGLSupported() is no longer in a utils package
and autoDetectRenderer now returns a promise
let me know if someone needs help/getting this to work with pixi 8
I started porting the library to typescript and using pixijs 8.
there are some breaking changes but nothing that cant be fixed
isWebGLSupported()is no longer in a utils package andautoDetectRenderernow returns a promiselet me know if someone needs help/getting this to work with pixi 8
That nice to hear, but did you maybe forget to push the changes? (the latest commit is 2 years old)
That nice to hear, but did you maybe forget to push the changes? (the latest commit is 2 years old)
I am not the maintainer
That nice to hear, but did you maybe forget to push the changes? (the latest commit is 2 years old)
I am not the maintainer
Oh. I understood that you ported made changes so that this repo supports pixijs 8 and has type declarations. :(
That nice to hear, but did you maybe forget to push the changes? (the latest commit is 2 years old)
I am not the maintainer
Oh. I understood that you ported made changes so that this repo supports pixijs 8 and has type declarations. :(
Once I figure out the bugs I can post the code, but I am also keeping it bare bones as some things are not working correctly or I don't understand how they work (double buffering eg)
Unfortunately I am kinda stuck First it looks fine, but zooming in is distorting the image and the marker jumps around.
I also tried updating the original L.PixiOverlay.js to pixi@8 without touching most of the original code, but then I get the following error
core.mjs:6516 ERROR TypeError: Cannot read properties of undefined (reading 'updateLocalTransform')
at WebGLRenderer.render (AbstractRenderer.js:82:25)
Maybe @manubb would be so kind and can help upgrading to pixi8?
Usage is
const pixiOverlay = new PixiOverlay(this.pixiContainer);
pixiOverlay.afterDrawCallback((callback) => {
if (firstDraw) {
const coords = callback.latLngToLayerPoint(
latLng([50, 8]),
);
circle.x = coords.x;
circle.y = coords.y;
}
if (firstDraw || prevZoom !== callback.map.getZoom()) {
circle.scale.set(1 / callback.getScale());
}
firstDraw = false;
prevZoom = callback.map.getZoom();
callback.renderer.render(callback.container);
});
Below is the current code
import {
Bounds,
Browser,
DomUtil,
LatLng,
latLng,
Layer,
LeafletEventHandlerFn,
Map,
Point,
ZoomAnimEvent,
} from 'leaflet';
import {
autoDetectRenderer,
Container,
isWebGLSupported,
Renderer,
} from 'pixi.js';
type AfterDrawCallback = (callback: Callback, event?: unknown) => void;
declare module 'leaflet' {
interface Map {
_getNewPixelOrigin(center: LatLng, zoom: number): Point;
}
interface Point {
_round(): Point;
}
}
interface Callback {
renderer: Renderer;
map: Map;
container: Container;
getScale: () => number;
layerPointToLatLng: (point: Point) => LatLng;
latLngToLayerPoint: (latLng: LatLng) => Point;
}
interface PixiOverlayOptions {
padding?: number;
forceCanvas?: boolean;
doubleBuffering?: boolean;
resolution?: number;
projectionZoom?: (map: Map) => number;
destroyInteractionManager?: boolean;
autoPreventDefault?: boolean;
preserveDrawingBuffer?: boolean;
clearBeforeRender?: boolean;
shouldRedrawOnMove?: (event: Event) => boolean;
}
interface RendererOptions {
resolution: number;
antialias: boolean;
forceCanvas: boolean;
preserveDrawingBuffer: boolean;
clearBeforeRender: boolean;
backgroundAlpha: number;
}
export class PixiOverlay extends Layer {
private readonly doubleBuffering: boolean;
private readonly pixiContainer: Container;
private drawCallback: AfterDrawCallback | undefined;
private rendererOptions: RendererOptions;
private renderer!: Renderer;
private auxRenderer!: Renderer;
private container!: HTMLElement;
private initialZoom: number = 0;
private wgsInitialShift: Point = new Point(0, 0);
private wgsOrigin: LatLng = latLng([0, 0]);
private zoom: number = 0;
private center: LatLng = latLng([0, 0]);
private roundFn = Point.prototype._round;
declare _map: Map;
pixiOptions: Required<PixiOverlayOptions> = {
padding: 0.1,
forceCanvas: false,
doubleBuffering: false,
resolution: Browser.retina ? 2 : 1,
projectionZoom: (map: Map) => {
if (map.getMaxZoom() === Infinity) return map.getMinZoom() + 8;
return (map.getMaxZoom() + map.getMinZoom()) / 2;
},
destroyInteractionManager: false,
autoPreventDefault: true,
preserveDrawingBuffer: false,
clearBeforeRender: true,
shouldRedrawOnMove: () => false,
};
constructor(pixiContainer: Container, options?: PixiOverlayOptions) {
super();
this.pixiOptions = { ...this.pixiOptions, ...options };
this.pixiContainer = pixiContainer;
this.rendererOptions = {
resolution: this.pixiOptions.resolution,
antialias: true,
forceCanvas: this.pixiOptions.forceCanvas,
preserveDrawingBuffer: this.pixiOptions.preserveDrawingBuffer,
clearBeforeRender: this.pixiOptions.clearBeforeRender,
backgroundAlpha: 0,
};
this.doubleBuffering =
isWebGLSupported() &&
!this.pixiOptions.forceCanvas &&
this.pixiOptions.doubleBuffering;
}
afterDrawCallback(callback: AfterDrawCallback): void {
this.drawCallback = callback;
}
destroy(): void {
this.onRemove();
}
override onAdd(map: Map): this {
this._map = map;
this.container = DomUtil.create('div', 'leaflet-pixi-overlay');
this.container.style.position = 'absolute';
const rendererPromise = autoDetectRenderer(this.rendererOptions).then(
(renderer) => {
// set event system
this.renderer = renderer;
this.container.appendChild(
this.renderer.view.canvas as HTMLCanvasElement,
);
},
);
if (map.options.zoomAnimation) {
DomUtil.addClass(this.container, 'leaflet-zoom-animated');
}
let auxRendererPromise = Promise.resolve();
if (this.doubleBuffering) {
auxRendererPromise = autoDetectRenderer(this.rendererOptions).then(
(renderer) => {
// set event system
this.auxRenderer = renderer;
this.container.appendChild(
this.auxRenderer.view.canvas as HTMLCanvasElement,
);
},
);
}
Promise.all([rendererPromise, auxRendererPromise]).then(() => {
map.getPanes().overlayPane.appendChild(this.container);
this.initialZoom = this.pixiOptions.projectionZoom(map);
this.wgsInitialShift = map.project(this.wgsOrigin, this.initialZoom);
this.update();
});
return this;
}
override onRemove(): this {
this.renderer.destroy();
this.auxRenderer?.destroy();
DomUtil.remove(this.container);
return this;
}
override getEvents(): { [p: string]: LeafletEventHandlerFn } {
const events: { [p: string]: LeafletEventHandlerFn } = {
zoom: this.onZoom,
move: this.onMove,
moveend: this.update,
};
if (this._map.options.zoomAnimation) {
events['zoomanim'] = this.onAnimZoom as LeafletEventHandlerFn;
// delete events['zoom'];
}
return events;
}
private onZoom(): void {
this.updateTransform(this._map.getCenter(), this._map.getZoom());
}
private onAnimZoom(event: ZoomAnimEvent): void {
this.updateTransform(event.center, event.zoom);
}
private onMove(): void {}
private updateTransform(center: LatLng, zoom: number): void {
const scale = this._map.getZoomScale(zoom, this.zoom);
const viewHalf = this._map
.getSize()
.multiplyBy(0.5 + this.pixiOptions.padding);
const currentCenterPoint = this._map.project(this.center, zoom);
const topLeftOffset = viewHalf
.multiplyBy(-scale)
.add(currentCenterPoint)
.subtract(this._map._getNewPixelOrigin(center, zoom));
if (Browser.any3d) {
DomUtil.setTransform(this.container, topLeftOffset, scale);
} else {
DomUtil.setPosition(this.container, topLeftOffset);
}
}
private update(): void {
const padding = this.pixiOptions.padding;
const mapSize = this._map.getSize();
const min = this._map
.containerPointToLayerPoint(mapSize.multiplyBy(-padding))
.round();
const bounds = new Bounds(
min,
min.add(mapSize.multiplyBy(1 + padding * 2)).round(),
);
this.center = this._map.getCenter();
this.zoom = this._map.getZoom();
if (this.doubleBuffering) {
const currentRenderer = this.renderer;
this.renderer = this.auxRenderer;
this.auxRenderer = currentRenderer;
}
const container = this.container;
const size = bounds.getSize();
if (
this.renderer.screen.x !== size.x ||
this.renderer.screen.y !== size.y
) {
// if ('gl' in this.renderer) {
// this.renderer.resolution = this.pixiOptions.resolution;
// }
this.renderer.resize(size.x, size.y);
}
this.redraw(bounds.min!);
DomUtil.setPosition(container, bounds.min!);
}
private redraw(offset: Point): void {
this.disableRounding();
const scale = this._map.getZoomScale(this.zoom, this.initialZoom);
const shift = this._map
.latLngToLayerPoint(this.wgsOrigin)
.subtract(this.wgsInitialShift.multiplyBy(scale))
.subtract(offset);
this.pixiContainer.scale.set(scale);
this.pixiContainer.position.set(shift.x, shift.y);
this.triggerUpdate();
this.enableRounding();
}
private disableRounding(): void {
Point.prototype._round = this.noRound;
}
private enableRounding(): void {
Point.prototype._round = this.roundFn;
}
private noRound = function (this: Point): Point {
return this;
};
private triggerUpdate(): void {
if (this.drawCallback) {
this.drawCallback({
renderer: this.renderer!,
map: this._map,
container: this.pixiContainer,
getScale: () =>
this._map.getZoomScale(this._map.getZoom(), this.initialZoom),
layerPointToLatLng: (point) => {
return this._map.unproject(point, this.initialZoom);
},
latLngToLayerPoint: (latLng) => {
return this._map.project(latLng, this.initialZoom);
},
});
}
}
}
Unfortunately I am kinda stuck First it looks fine, but zooming in is distorting the image and the marker jumps around.
I also tried updating the original L.PixiOverlay.js to pixi@8 without touching most of the original code, but then I get the following error
core.mjs:6516 ERROR TypeError: Cannot read properties of undefined (reading 'updateLocalTransform') at WebGLRenderer.render (AbstractRenderer.js:82:25)Maybe @manubb would be so kind and can help upgrading to pixi8?
Not a gl expert but I've seen the kind of behavior in the video when messing about with shaders on markers.
I have managed to to get a working version with pixi 8 using leaflet 2.0.0.
import L from 'leaflet'
import * as PIXI from 'pixi.js';
// Leaflet.PixiOverlay
// version: 2.0.0
// author: Manuel Baclet <[email protected]>
// license: MIT
const round = L.Point.prototype._round;
const no_round = function () {
return this;
};
function setEventSystem(renderer, destroyInteractionManager, autoPreventDefault) {
const eventSystem = renderer.events;
if (destroyInteractionManager) {
eventSystem.destroy();
} else if (!autoPreventDefault) {
eventSystem.autoPreventDefault = false;
}
}
function projectionZoom(map) {
const maxZoom = map.getMaxZoom();
const minZoom = map.getMinZoom();
if (maxZoom === Infinity) return minZoom + 8;
return (maxZoom + minZoom) / 2;
}
const pixiOverlayClass = {
options: {
// @option padding: Number = 0.1
// How much to extend the clip area around the map view (relative to its size)
// e.g. 0.1 would be 10% of map view in each direction
padding: 0.1,
// @option doubleBuffering: Boolean = false
// Help to prevent flicker when refreshing display on some devices (e.g. iOS devices)
doubleBuffering: false,
// @option resolution: Number = 1
// Resolution of the renderer canvas
resolution: L.Browser.retina ? 2 : 1,
// @option projectionZoom(map: map): Number
// return the layer projection zoom level
projectionZoom: projectionZoom,
// @option destroyInteractionManager: Boolean = false
// Destroy PIXI EventSystem
destroyInteractionManager: false,
// @option
// Customize PIXI EventSystem autoPreventDefault property
// This option is ignored if destroyInteractionManager is set
autoPreventDefault: false,
// @option preserveDrawingBuffer: Boolean = false
// Enables drawing buffer preservation
preserveDrawingBuffer: false,
// @option clearBeforeRender: Boolean = true
// Clear the canvas before the new render pass
clearBeforeRender: true,
// @option shouldRedrawOnMove(e: moveEvent): Boolean
// filter move events that should trigger a layer redraw
shouldRedrawOnMove: function () {
return false;
},
},
initialize: function (drawCallback, pixiContainer, renderer, auxRenderer, options) {
L.Util.setOptions(this, options);
L.Util.stamp(this);
this._drawCallback = drawCallback;
this._pixiContainer = pixiContainer;
this._renderer = renderer;
this._auxRenderer = auxRenderer;
this._doubleBuffering = !!auxRenderer && this.options.doubleBuffering;
},
_setMap: function () { },
_setContainerStyle: function () { },
_addContainer: function () {
this.getPane().appendChild(this._container);
},
_setEvents: function () { },
onAdd: function (targetMap) {
this._setMap(targetMap);
if (!this._container) {
const container = (this._container = L.DomUtil.create(
'div',
'leaflet-pixi-overlay'
));
container.style.position = 'absolute';
setEventSystem(
this._renderer,
this.options.destroyInteractionManager,
this.options.autoPreventDefault
);
container.appendChild(this._renderer.canvas);
if (this._zoomAnimated) {
container.classList.add('leaflet-zoom-animated');
this._setContainerStyle();
}
if (this._doubleBuffering && this._auxRenderer) {
setEventSystem(
this._auxRenderer,
this.options.destroyInteractionManager,
this.options.autoPreventDefault
);
container.appendChild(this._auxRenderer.canvas);
this._renderer.canvas.style.position = 'absolute';
this._auxRenderer.canvas.style.position = 'absolute';
}
}
this._addContainer();
this._setEvents();
const map = this._map;
this._initialZoom = this.options.projectionZoom(map);
this._wgsOrigin = new L.LatLng(0, 0);
this._wgsInitialShift = map.project(this._wgsOrigin, this._initialZoom);
this._mapInitialZoom = map.getZoom();
const _layer = this;
this.utils = {
latLngToLayerPoint: function (latLng, zoom) {
zoom = zoom === undefined ? _layer._initialZoom : zoom;
const projectedPoint = map.project((latLng), zoom);
return projectedPoint;
},
layerPointToLatLng: function (point, zoom) {
zoom = zoom === undefined ? _layer._initialZoom : zoom;
const projectedPoint = L.point(point);
return map.unproject(projectedPoint, zoom);
},
getScale: function (zoom) {
if (zoom === undefined)
return map.getZoomScale(map.getZoom(), _layer._initialZoom);
else return map.getZoomScale(zoom, _layer._initialZoom);
},
getRenderer: function () {
return _layer._renderer;
},
getContainer: function () {
return _layer._pixiContainer;
},
getMap: function () {
return _layer._map;
},
};
this._update({ type: 'add' });
},
onRemove: function () {
L.DomUtil.remove(this._container);
},
_onAnimZoom: function (e) {
// Store animation state
this._animatingZoom = true;
this._animationCenter = e.center;
this._animationZoom = e.zoom;
this._updateTransform(e.center, e.zoom);
},
_onZoom: function () {
// Clear animation state
this._animatingZoom = false;
this._updateTransform(this._map.getCenter(), this._zoom);
},
getEvents: function () {
const events = {
zoom: this._onZoom,
move: this._onMove,
moveend: this._onMoveEnd,
zoomstart: this._onZoomStart,
zoomend: this._onZoomEnd,
};
if (this._zoomAnimated) {
events.zoomanim = this._onAnimZoom;
}
return events;
},
_onZoomStart: function () {
this._zooming = true;
},
_onZoomEnd: function () {
this._zooming = false;
this._animatingZoom = false;
this._update({ type: 'zoomend' });
},
_onMoveEnd: function (e) {
if (!this._zooming) {
this._update(e);
}
},
_onMove: function (e) {
if (this.options.shouldRedrawOnMove(e)) {
this._update(e);
}
},
_updateTransform: function (center, zoom) {
var scale = this._map.getZoomScale(zoom, this._zoom),
viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
currentCenterPoint = this._map.project(this._center, zoom),
topLeftOffset = viewHalf
.multiplyBy(-scale)
.add(currentCenterPoint)
.subtract(this._map._getNewPixelOrigin(center, zoom));
if (L.Browser.any3d || L.version >= '2') {
L.DomUtil.setTransform(this._container, topLeftOffset, scale);
} else {
L.DomUtil.setPosition(this._container, topLeftOffset);
}
},
_redraw: function (offset, e) {
this._disableLeafletRounding();
const scale = this._map.getZoomScale(this._zoom, this._initialZoom);
const shift = this._map
.latLngToLayerPoint(this._wgsOrigin)
._subtract(this._wgsInitialShift.multiplyBy(scale))
._subtract(offset);
this._pixiContainer.scale.set(scale);
this._pixiContainer.position.set(shift.x, shift.y);
this._drawCallback(this.utils, e);
this._enableLeafletRounding();
},
_update: function (e) {
// is this really useful?
if (this._map._animatingZoom && this._bounds) {
return;
}
// Update pixel bounds of renderer container
const p = this.options.padding;
const mapSize = this._map.getSize();
const min = this._map
.containerPointToLayerPoint(mapSize.multiplyBy(-p))
.round();
this._bounds = new L.Bounds(
min,
min.add(mapSize.multiplyBy(1 + p * 2)).round()
);
// Store previous values for comparison
const prevCenter = this._center;
const prevZoom = this._zoom;
this._center = this._map.getCenter();
this._zoom = this._map.getZoom();
// Only proceed with expensive operations if something actually changed
if (prevCenter && prevZoom !== undefined) {
const centerChanged = !prevCenter.equals(this._center);
const zoomChanged = prevZoom !== this._zoom;
if (!centerChanged && !zoomChanged && e.type !== 'add') {
return;
}
}
if (this._doubleBuffering && this._auxRenderer) {
const currentRenderer = this._renderer;
this._renderer = this._auxRenderer;
this._auxRenderer = currentRenderer;
}
const canvas = this._renderer.canvas;
const b = this._bounds;
const container = this._container;
const size = b.getSize();
if (
!this._renderer.size ||
this._renderer.size.x !== size.x ||
this._renderer.size.y !== size.y
) {
this._renderer.resize(size.x, size.y);
canvas.style.width = size.x + 'px';
canvas.style.height = size.y + 'px';
this._renderer.size = size;
}
if (this._doubleBuffering && this._auxRenderer) {
const self = this;
requestAnimationFrame(function () {
self._redraw(b.min, e);
if (self._renderer.gl) {
self._renderer.gl.finish();
}
canvas.style.visibility = 'visible';
self._auxRenderer.canvas.style.visibility = 'hidden';
L.DomUtil.setPosition(container, b.min);
});
} else {
this._redraw(b.min, e);
L.DomUtil.setPosition(container, b.min);
}
},
_disableLeafletRounding: function () {
L.Point.prototype._round = no_round;
},
_enableLeafletRounding: function () {
L.Point.prototype._round = round;
},
redraw: function (data) {
if (this._map) {
this._disableLeafletRounding();
this._drawCallback(this.utils, data);
this._enableLeafletRounding();
}
return this;
},
_destroy: function () {
this._renderer.destroy();
if (this._auxRenderer) {
this._auxRenderer.destroy();
}
},
destroy: function () {
this.remove();
this._destroy();
},
};
// Extend based on Leaflet version
let PixiOverlay;
if (L.version >= '1') {
PixiOverlay = L.Layer.extend(pixiOverlayClass);
} else {
// backport some [email protected] methods
L.Map.prototype.getZoomScale = function (toZoom, fromZoom) {
const crs = this.options.crs;
fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
return crs.scale(toZoom) / crs.scale(fromZoom);
};
L.DomUtil.setTransform = function (el, offset, scale) {
const pos = offset || new L.Point(0, 0);
el.style[L.DomUtil.TRANSFORM] =
(L.Browser.ie3d
? 'translate(' + pos.x + 'px,' + pos.y + 'px)'
: 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
(scale ? ' scale(' + scale + ')' : '');
};
// patch pixiOverlayClass for [email protected]
pixiOverlayClass.includes = L.Mixin.Events;
pixiOverlayClass.addTo = function (map) {
map.addLayer(this);
return this;
};
pixiOverlayClass._setMap = function (map) {
this._map = map;
this._zoomAnimated = map._zoomAnimated;
};
pixiOverlayClass._setContainerStyle = function () {
const self = this;
[
'-webkit-transform-origin',
'-ms-transform-origin',
'transform-origin',
].forEach(function (property) {
self._container.style[property] = '0 0';
});
};
pixiOverlayClass._addContainer = function () {
this._map
.getPanes()
[this.options.pane || 'overlayPane'].appendChild(this._container);
};
pixiOverlayClass._setEvents = function () {
const events = this.getEvents();
for (const evt in events) {
this._map.on(evt, events[evt], this);
}
};
pixiOverlayClass.onRemove = function () {
const parent = this._container.parentNode;
if (parent) {
parent.removeChild(this._container);
}
const events = this.getEvents();
for (const evt in events) {
this._map.off(evt, events[evt], this);
}
this._map = null;
};
pixiOverlayClass.destroy = function () {
const map = this._map || this._mapToAdd;
if (map) {
map.removeLayer(this);
}
this._destroy();
};
PixiOverlay = L.Class.extend(pixiOverlayClass);
}
// Factory function
export function pixiOverlay(
drawCallback,
pixiContainer,
renderer,
auxRenderer = null,
options = {}
) {
return L.Browser.canvas
? new PixiOverlay(drawCallback, pixiContainer, renderer, auxRenderer, options)
: null;
}
export { PixiOverlay };
export default { pixiOverlay, PixiOverlay };
I moved the renderer initialization outside the module to avoid doing any async inside.
Unfortunately I am kinda stuck First it looks fine, but zooming in is distorting the image and the marker jumps around.
I also tried updating the original L.PixiOverlay.js to pixi@8 without touching most of the original code, but then I get the following error
core.mjs:6516 ERROR TypeError: Cannot read properties of undefined (reading 'updateLocalTransform') at WebGLRenderer.render (AbstractRenderer.js:82:25)Maybe @manubb would be so kind and can help upgrading to pixi8?
Usage is
const pixiOverlay = new PixiOverlay(this.pixiContainer); pixiOverlay.afterDrawCallback((callback) => { if (firstDraw) { const coords = callback.latLngToLayerPoint( latLng([50, 8]), ); circle.x = coords.x; circle.y = coords.y; }
if (firstDraw || prevZoom !== callback.map.getZoom()) { circle.scale.set(1 / callback.getScale()); }
firstDraw = false; prevZoom = callback.map.getZoom(); callback.renderer.render(callback.container); }); Below is the current code
import { Bounds, Browser, DomUtil, LatLng, latLng, Layer, LeafletEventHandlerFn, Map, Point, ZoomAnimEvent, } from 'leaflet'; import { autoDetectRenderer, Container, isWebGLSupported, Renderer, } from 'pixi.js';
type AfterDrawCallback = (callback: Callback, event?: unknown) => void;
declare module 'leaflet' { interface Map { _getNewPixelOrigin(center: LatLng, zoom: number): Point; }
interface Point { _round(): Point; } }
interface Callback { renderer: Renderer; map: Map; container: Container; getScale: () => number; layerPointToLatLng: (point: Point) => LatLng; latLngToLayerPoint: (latLng: LatLng) => Point; }
interface PixiOverlayOptions { padding?: number; forceCanvas?: boolean; doubleBuffering?: boolean; resolution?: number; projectionZoom?: (map: Map) => number; destroyInteractionManager?: boolean; autoPreventDefault?: boolean; preserveDrawingBuffer?: boolean; clearBeforeRender?: boolean; shouldRedrawOnMove?: (event: Event) => boolean; }
interface RendererOptions { resolution: number; antialias: boolean; forceCanvas: boolean; preserveDrawingBuffer: boolean; clearBeforeRender: boolean; backgroundAlpha: number; }
export class PixiOverlay extends Layer { private readonly doubleBuffering: boolean; private readonly pixiContainer: Container; private drawCallback: AfterDrawCallback | undefined; private rendererOptions: RendererOptions; private renderer!: Renderer; private auxRenderer!: Renderer; private container!: HTMLElement; private initialZoom: number = 0; private wgsInitialShift: Point = new Point(0, 0); private wgsOrigin: LatLng = latLng([0, 0]); private zoom: number = 0; private center: LatLng = latLng([0, 0]); private roundFn = Point.prototype._round; declare _map: Map;
pixiOptions: Required<PixiOverlayOptions> = { padding: 0.1, forceCanvas: false, doubleBuffering: false, resolution: Browser.retina ? 2 : 1, projectionZoom: (map: Map) => { if (map.getMaxZoom() === Infinity) return map.getMinZoom() + 8; return (map.getMaxZoom() + map.getMinZoom()) / 2; }, destroyInteractionManager: false, autoPreventDefault: true, preserveDrawingBuffer: false, clearBeforeRender: true, shouldRedrawOnMove: () => false, };
constructor(pixiContainer: Container, options?: PixiOverlayOptions) { super(); this.pixiOptions = { ...this.pixiOptions, ...options }; this.pixiContainer = pixiContainer;
this.rendererOptions = { resolution: this.pixiOptions.resolution, antialias: true, forceCanvas: this.pixiOptions.forceCanvas, preserveDrawingBuffer: this.pixiOptions.preserveDrawingBuffer, clearBeforeRender: this.pixiOptions.clearBeforeRender, backgroundAlpha: 0, }; this.doubleBuffering = isWebGLSupported() && !this.pixiOptions.forceCanvas && this.pixiOptions.doubleBuffering;}
afterDrawCallback(callback: AfterDrawCallback): void { this.drawCallback = callback; }
destroy(): void { this.onRemove(); }
override onAdd(map: Map): this { this._map = map;
this.container = DomUtil.create('div', 'leaflet-pixi-overlay'); this.container.style.position = 'absolute'; const rendererPromise = autoDetectRenderer(this.rendererOptions).then( (renderer) => { // set event system this.renderer = renderer; this.container.appendChild( this.renderer.view.canvas as HTMLCanvasElement, ); }, ); if (map.options.zoomAnimation) { DomUtil.addClass(this.container, 'leaflet-zoom-animated'); } let auxRendererPromise = Promise.resolve(); if (this.doubleBuffering) { auxRendererPromise = autoDetectRenderer(this.rendererOptions).then( (renderer) => { // set event system this.auxRenderer = renderer; this.container.appendChild( this.auxRenderer.view.canvas as HTMLCanvasElement, ); }, ); } Promise.all([rendererPromise, auxRendererPromise]).then(() => { map.getPanes().overlayPane.appendChild(this.container); this.initialZoom = this.pixiOptions.projectionZoom(map); this.wgsInitialShift = map.project(this.wgsOrigin, this.initialZoom); this.update(); }); return this;}
override onRemove(): this { this.renderer.destroy(); this.auxRenderer?.destroy(); DomUtil.remove(this.container); return this; }
override getEvents(): { [p: string]: LeafletEventHandlerFn } { const events: { [p: string]: LeafletEventHandlerFn } = { zoom: this.onZoom, move: this.onMove, moveend: this.update, };
if (this._map.options.zoomAnimation) { events['zoomanim'] = this.onAnimZoom as LeafletEventHandlerFn; // delete events['zoom']; } return events;}
private onZoom(): void { this.updateTransform(this._map.getCenter(), this._map.getZoom()); }
private onAnimZoom(event: ZoomAnimEvent): void { this.updateTransform(event.center, event.zoom); }
private onMove(): void {}
private updateTransform(center: LatLng, zoom: number): void { const scale = this._map.getZoomScale(zoom, this.zoom); const viewHalf = this._map .getSize() .multiplyBy(0.5 + this.pixiOptions.padding); const currentCenterPoint = this._map.project(this.center, zoom); const topLeftOffset = viewHalf .multiplyBy(-scale) .add(currentCenterPoint) .subtract(this._map._getNewPixelOrigin(center, zoom));
if (Browser.any3d) { DomUtil.setTransform(this.container, topLeftOffset, scale); } else { DomUtil.setPosition(this.container, topLeftOffset); }}
private update(): void { const padding = this.pixiOptions.padding; const mapSize = this._map.getSize(); const min = this._map .containerPointToLayerPoint(mapSize.multiplyBy(-padding)) .round(); const bounds = new Bounds( min, min.add(mapSize.multiplyBy(1 + padding * 2)).round(), ); this.center = this._map.getCenter(); this.zoom = this._map.getZoom();
if (this.doubleBuffering) { const currentRenderer = this.renderer; this.renderer = this.auxRenderer; this.auxRenderer = currentRenderer; } const container = this.container; const size = bounds.getSize(); if ( this.renderer.screen.x !== size.x || this.renderer.screen.y !== size.y ) { // if ('gl' in this.renderer) { // this.renderer.resolution = this.pixiOptions.resolution; // } this.renderer.resize(size.x, size.y); } this.redraw(bounds.min!); DomUtil.setPosition(container, bounds.min!);}
private redraw(offset: Point): void { this.disableRounding(); const scale = this._map.getZoomScale(this.zoom, this.initialZoom); const shift = this._map .latLngToLayerPoint(this.wgsOrigin) .subtract(this.wgsInitialShift.multiplyBy(scale)) .subtract(offset); this.pixiContainer.scale.set(scale); this.pixiContainer.position.set(shift.x, shift.y); this.triggerUpdate(); this.enableRounding(); }
private disableRounding(): void { Point.prototype._round = this.noRound; }
private enableRounding(): void { Point.prototype._round = this.roundFn; }
private noRound = function (this: Point): Point { return this; };
private triggerUpdate(): void { if (this.drawCallback) { this.drawCallback({ renderer: this.renderer!, map: this._map, container: this.pixiContainer, getScale: () => this._map.getZoomScale(this._map.getZoom(), this.initialZoom), layerPointToLatLng: (point) => { return this._map.unproject(point, this.initialZoom); }, latLngToLayerPoint: (latLng) => { return this._map.project(latLng, this.initialZoom); }, }); } } }
This is because the scale value of the sprite becomes very low after when you zoom in more and more. It is better to keep the scale of the sprite constant by not scaling the stage. Otherwise the graphics will also be distorted and even be invisible like the sprites.