My approach provides a solution that does not rely on the archived saveSvgAsPng library and does not require adding the svg element to the DOM!
I successfully tested this method with OpenStreetMap maps rendered using ngx-leaflet.
HTML:
<div leaflet id="map" class="map" [leafletOptions]="leafletOptions"
(leafletMapReady)="onMapReady($event)"
(leafletMapMoveEnd)="updateSvgMap()"
(leafletMouseUp)="updateSvgMap()">
</div>
Component:
map: L.Map | undefined;
leafletOptions: MapOptions = {
layers: this.getLayers(),
center: [38.8951, -77.0364],
zoom: 18
}
onMapReady(map: L.Map) {
this.map = map;
}
updateSvgMap() {
const map: L.Map | undefined = this.map;
if (!map) {
return;
}
const mapSvgElement: SVGElement = this.createSvgElement(map);
this.convertCanvasToBlob(mapSvgElement).then((blob: Blob | null) => {
console.log(blob);
}, (error: Error) => {
console.log(error);
});
}
//create svg element from Map
private createSvgElement(map: L.Map): SVGElement {
const defaultNameSpace = 'http://www.w3.org/2000/svg';
const xlinkNameSpace = 'http://www.w3.org/1999/xlink';
const mapContainerRect: DOMRect = map.getContainer().getBoundingClientRect();
const svgElement: SVGElement = document.createElementNS(defaultNameSpace,'svg');
svgElement.setAttribute('height', mapContainerRect.height.toString());
svgElement.setAttribute('width', mapContainerRect.width.toString());
//svgElement.setAttribute('id','svgMap'); //only if needed to reference it later
svgElement.setAttributeNS(xlinkNameSpace, "xlink:href", "link")
let mapTiles: NodeListOf<Element> = document.querySelectorAll('.leaflet-tile-loaded');
let markers: NodeListOf<Element> = document.querySelectorAll('.leaflet-marker-icon');
mapTiles.forEach((tile: Element) => {
const image: SVGImageElement = document.createElementNS(defaultNameSpace, 'image');
const tileRect: DOMRect = tile.getBoundingClientRect();
image.setAttribute('width', tileRect.width.toString());
image.setAttribute('height', tileRect.width.toString());
image.setAttribute('x', (tileRect.left - mapContainerRect.left).toString());
image.setAttribute('y', (tileRect.top - mapContainerRect.top).toString());
image.setAttributeNS(xlinkNameSpace, 'href', (tile as any)?.src);
svgElement.appendChild(image);
});
markers.forEach((marker: Element) => {
const image: SVGImageElement = document.createElementNS(defaultNameSpace, 'image');
const markerRect: DOMRect = marker.getBoundingClientRect();
image.setAttribute('width', markerRect.width.toString());
image.setAttribute('height', markerRect.height.toString());
image.setAttribute('x', (markerRect.left - mapContainerRect.left).toString());
image.setAttribute('y', (markerRect.top - mapContainerRect.top).toString());
image.setAttributeNS(xlinkNameSpace, 'href',(marker as any)?.src);
svgElement.appendChild(image);
});
return svgElement;
}
//convert svg with all inline images to Image-Blob
private convertCanvasToBlob(svgElement: SVGElement): Promise<Blob | null> {
return new Promise((resolve, reject) => {
const parser: DOMParser = new DOMParser();
const svgString: string = new XMLSerializer().serializeToString(svgElement);
const svgDoc: Document = parser.parseFromString(svgString, 'image/svg+xml');
const svgImage: HTMLImageElement = new Image();
svgImage.onload = () => {
const canvas: HTMLCanvasElement = document.createElement('canvas');
const context: CanvasRenderingContext2D | null = canvas.getContext('2d');
if (!context) {
return;
}
canvas.width = svgImage.width;
canvas.height = svgImage.height;
context.drawImage(svgImage, 0, 0);
//const pngBase64String: string = canvas.toDataURL('image/png');
//console.log(pngBase64String);
canvas.toBlob((blob: Blob | null) => {
resolve(blob);
}, 'image/png'); //or jpeg or webp
};
svgImage.onerror = (error: string | Event) => {
if (typeof error === 'string') {
reject(Error(error));
} else {
reject(Error('Could not load image' + error.type));
}
}
const imageElements: HTMLCollectionOf<SVGImageElement> = svgDoc.getElementsByTagName('image');
let imagesLoaded = 0;
function checkAllImagesLoaded() {
imagesLoaded++;
if (imagesLoaded === imageElements.length) {
svgImage.src = 'data:image/svg+xml;base64,' + btoa(new XMLSerializer().serializeToString(svgDoc));
}
}
for (let i: number = 0; i < imageElements.length; i++) {
const image: HTMLImageElement = new Image();
image.crossOrigin = "Anonymous";
image.onload = () => {
const canvas: HTMLCanvasElement = document.createElement('canvas');
const context: CanvasRenderingContext2D | null = canvas.getContext('2d');
if (!context) {
checkAllImagesLoaded();
return;
}
canvas.width = image.width;
canvas.height = image.height;
context.drawImage(image, 0, 0);
const imageDataURL = canvas.toDataURL('image/png');
imageElements[i].setAttributeNS('http://www.w3.org/1999/xlink', 'href', imageDataURL);
checkAllImagesLoaded();
};
image.onerror = (error: string | Event) => {
if (typeof error === 'string') {
console.log(error);
} else {
console.log('Could not load image' + error.type);
}
checkAllImagesLoaded();
}
let imageHref: string | null = imageElements[i].getAttribute('xlink:href');
if (!imageHref) {
imageHref = imageElements[i].getAttribute('href');
}
if (imageHref) {
image.src = imageHref;
}
}
});
}