forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			130 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			130 lines
		
	
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
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<MinimapImplementation> {
 | 
						|
        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<Loc>(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(<MinimapImplementation>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<string>
 | 
						|
    public async CreatePng(format: "blob"): Promise<Blob>
 | 
						|
    public async CreatePng(format: "image" | "blob"): Promise<string | Blob>
 | 
						|
    public async CreatePng(format: "image" | "blob"): Promise<string | Blob> {
 | 
						|
        // 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<string | Blob>((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)
 | 
						|
        })
 | 
						|
    }
 | 
						|
}
 |