import FeaturePipelineState from "../Logic/State/FeaturePipelineState" import MinimapImplementation from "../UI/Base/MinimapImplementation" import { UIEventSource } from "../Logic/UIEventSource" import Loc from "../Models/Loc" import ShowDataLayer from "../UI/ShowDataLayer/ShowDataLayer" import { BBox } from "../Logic/BBox" import Minimap from "../UI/Base/Minimap" import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers" import { Utils } from "../Utils" export interface PngMapCreatorOptions { readonly divId: string readonly width: number readonly height: number readonly scaling?: 1 | number readonly dummyMode?: boolean } export class PngMapCreator { private readonly _state: FeaturePipelineState | undefined private readonly _options: PngMapCreatorOptions constructor(state: FeaturePipelineState | undefined, options: PngMapCreatorOptions) { this._state = state this._options = { ...options, scaling: options.scaling ?? 1 } } /** * Creates a minimap, waits till all needed tiles are loaded before returning * @private */ private async createAndLoadMinimap(): Promise { const state = this._state const options = this._options const baselayer = AvailableBaseLayers.layerOverview.find( (bl) => bl.id === state.layoutToUse.defaultBackgroundId ) ?? AvailableBaseLayers.osmCarto return new Promise((resolve) => { const minimap = Minimap.createMiniMap({ location: new UIEventSource(state.locationControl.data), // We remove the link between the old and the new UI-event source as moving the map while the export is running fucks up the screenshot background: new UIEventSource(baselayer), allowMoving: false, onFullyLoaded: (_) => window.setTimeout(() => { resolve(minimap) }, 250), }) const style = `width: ${options.width * options.scaling}mm; height: ${ options.height * options.scaling }mm;` minimap.SetStyle(style) minimap.AttachTo(options.divId) }) } /** * Creates a base64-encoded PNG image * @constructor */ public async CreatePng(format: "image"): Promise public async CreatePng(format: "blob"): Promise public async CreatePng(format: "image" | "blob"): Promise public async CreatePng(format: "image" | "blob"): Promise { // Lets first init the minimap and wait for all background tiles to load const minimap = await this.createAndLoadMinimap() const state = this._state const dummyMode = this._options.dummyMode ?? false return new Promise((resolve, reject) => { // Next: we prepare the features. Only fully contained features are shown minimap.leafletMap.addCallbackAndRunD(async (leaflet) => { // Ping the featurepipeline to download what is needed if (dummyMode) { console.warn("Dummy mode is active - not loading map layers") } else { const bounds = BBox.fromLeafletBounds( leaflet.getBounds().pad(0.1).pad(-state.layoutToUse.widenFactor) ) state.currentBounds.setData(bounds) if (!state.featurePipeline.sufficientlyZoomed.data) { console.warn("Not sufficiently zoomed!") } if (state.featurePipeline.runningQuery.data) { // A query is running! // Let's wait for it to complete console.log("Waiting for the query to complete") await state.featurePipeline.runningQuery.AsPromise( (isRunning) => !isRunning ) console.log("Query has completeted!") } state.featurePipeline.GetTilesPerLayerWithin(bounds, (tile) => { if (tile.layer.layerDef.id.startsWith("note_import")) { // Don't export notes to import return } new ShowDataLayer({ features: tile, leafletMap: minimap.leafletMap, layerToShow: tile.layer.layerDef, doShowLayer: tile.layer.isDisplayed, state: undefined, }) }) await Utils.waitFor(2000) } minimap .TakeScreenshot(format) .then(async (result) => { const divId = this._options.divId await Utils.waitFor(250) document .getElementById(divId) .removeChild( /*Will fetch the cached htmlelement:*/ minimap.ConstructElement() ) return resolve(result) }) .catch((failreason) => { console.error("Could no make a screenshot due to ", failreason) reject(failreason) }) }) state.AddAllOverlaysToMap(minimap.leafletMap) }) } }