126 lines
4.5 KiB
TypeScript
126 lines
4.5 KiB
TypeScript
import ThemeViewState from "../Models/ThemeViewState"
|
|
import { Utils } from "../Utils"
|
|
import { UIEventSource } from "../Logic/UIEventSource"
|
|
import { Map as MlMap } from "maplibre-gl"
|
|
import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor"
|
|
import { AvailableRasterLayers } from "../Models/RasterLayers"
|
|
|
|
export interface PngMapCreatorOptions {
|
|
/**
|
|
* In mm
|
|
*/
|
|
readonly width: number
|
|
/**
|
|
* In mm
|
|
*/
|
|
readonly height: number
|
|
}
|
|
|
|
export class PngMapCreator {
|
|
private static id = 0
|
|
private readonly _options: PngMapCreatorOptions
|
|
private readonly _state: ThemeViewState
|
|
|
|
constructor(state: ThemeViewState, options: PngMapCreatorOptions) {
|
|
this._state = state
|
|
this._options = options
|
|
}
|
|
|
|
/**
|
|
* Creates a base64-encoded PNG image
|
|
* @constructor
|
|
*/
|
|
public async CreatePng(freeComponentId: string, status?: UIEventSource<string>): Promise<Blob> {
|
|
const div = document.createElement("div")
|
|
div.id = "mapdiv-" + PngMapCreator.id
|
|
|
|
/**
|
|
* We want a certain amount of pixels per mm² for a high print quality
|
|
* For this, we create a bigger map on the screen with a canvas, which has a pixelratio given
|
|
*
|
|
* We know that the default DPI of a canvas is 92, but to print something, we need a bit more
|
|
* So, instead, we give it PIXELRATIO more mm and let it render.
|
|
* We then draw this onto the PDF as if it were smaller, so it'll have plenty of quality there.
|
|
*
|
|
* However, we also need to compensate for this in the zoom level
|
|
*
|
|
*/
|
|
|
|
const pixelRatio = 2 // dots per mm
|
|
div.style.width = this._options.width + "mm"
|
|
div.style.height = this._options.height + "mm"
|
|
|
|
PngMapCreator.id++
|
|
try {
|
|
const layout = this._state.layout
|
|
|
|
function setState(msg: string) {
|
|
status?.setData(layout.id + ": " + msg)
|
|
}
|
|
|
|
setState("Initializing map")
|
|
|
|
const settings = this._state.mapProperties
|
|
const l = settings.location.data
|
|
|
|
document.getElementById(freeComponentId).appendChild(div)
|
|
const newZoom = settings.zoom.data + Math.log2(pixelRatio) - 1
|
|
const rasterLayerProperties =
|
|
settings.rasterLayer.data?.properties ??
|
|
AvailableRasterLayers.defaultBackgroundLayer.properties
|
|
const style = rasterLayerProperties?.style ?? rasterLayerProperties?.url
|
|
const mapElem = new MlMap({
|
|
container: div.id,
|
|
style,
|
|
center: [l.lon, l.lat],
|
|
zoom: newZoom,
|
|
pixelRatio,
|
|
})
|
|
|
|
console.log("Creating a map with size", this._options.width, this._options.height)
|
|
|
|
const map = new UIEventSource<MlMap>(mapElem)
|
|
const mla = new MapLibreAdaptor(map)
|
|
mla.zoom.setData(newZoom)
|
|
mla.location.setData(settings.location.data)
|
|
mla.rasterLayer.setData(settings.rasterLayer.data)
|
|
mla.allowZooming.setData(false)
|
|
mla.allowMoving.setData(false)
|
|
|
|
setState("Waiting for the data")
|
|
this._state?.showNormalDataOn(map)
|
|
setState("Waiting for the data")
|
|
|
|
await this._state.dataIsLoading.AsPromise((loading) => !loading)
|
|
setState("Waiting for styles to be fully loaded")
|
|
while (!map?.data?.isStyleLoaded()) {
|
|
console.log("Waiting for the style to be loaded...")
|
|
await Utils.waitFor(250)
|
|
}
|
|
|
|
// Some extra buffer...
|
|
for (let i = 0; i < 5; i++) {
|
|
setState(5 - i + " seconds pause to make sure all images are loaded...")
|
|
await Utils.waitFor(1000)
|
|
}
|
|
setState(
|
|
"Exporting png (" +
|
|
this._options.width +
|
|
"mm * " +
|
|
this._options.height +
|
|
"mm , maplibre-canvas-pixelratio: " +
|
|
pixelRatio +
|
|
")"
|
|
)
|
|
const progress = new UIEventSource<{ current: number; total: number }>(undefined)
|
|
progress.addCallbackD(({ current, total }) => {
|
|
setState(`Rendering marker ${current}/${total}`)
|
|
})
|
|
const png = await mla.exportAsPng(pixelRatio, progress)
|
|
setState("Offering as download...")
|
|
return png
|
|
} finally {
|
|
div.parentElement.removeChild(div)
|
|
}
|
|
}
|
|
}
|