forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			113 lines
		
	
	
	
		
			5.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			113 lines
		
	
	
	
		
			5.2 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)
 | 
						|
        })
 | 
						|
    }
 | 
						|
}
 |