mapcomplete/Utils/pngMapCreator.ts

131 lines
5.6 KiB
TypeScript
Raw Normal View History

2022-10-27 01:50:01 +02:00
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"
2022-09-14 12:18:51 +02:00
2022-10-27 01:50:01 +02:00
export interface PngMapCreatorOptions {
readonly divId: string
readonly width: number
readonly height: number
readonly scaling?: 1 | number
2022-09-14 12:18:51 +02:00
readonly dummyMode?: boolean
}
2022-09-12 20:14:03 +02:00
export class PngMapCreator {
2022-10-27 01:50:01 +02:00
private readonly _state: FeaturePipelineState | undefined
private readonly _options: PngMapCreatorOptions
2022-09-12 20:14:03 +02:00
2022-09-14 12:18:51 +02:00
constructor(state: FeaturePipelineState | undefined, options: PngMapCreatorOptions) {
2022-10-27 01:50:01 +02:00
this._state = state
this._options = { ...options, scaling: options.scaling ?? 1 }
2022-09-12 20:14:03 +02:00
}
/**
* Creates a minimap, waits till all needed tiles are loaded before returning
* @private
*/
private async createAndLoadMinimap(): Promise<MinimapImplementation> {
2022-10-27 01:50:01 +02:00
const state = this._state
2022-09-12 20:14:03 +02:00
const options = this._options
2022-10-27 01:50:01 +02:00
const baselayer =
AvailableBaseLayers.layerOverview.find(
(bl) => bl.id === state.layoutToUse.defaultBackgroundId
) ?? AvailableBaseLayers.osmCarto
return new Promise((resolve) => {
2022-09-12 20:14:03 +02:00
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
2022-09-13 00:42:29 +02:00
background: new UIEventSource(baselayer),
2022-09-12 20:14:03 +02:00
allowMoving: false,
onFullyLoaded: (_) =>
window.setTimeout(() => {
resolve(<MinimapImplementation>minimap)
2022-10-27 01:50:01 +02:00
}, 250),
2022-09-12 20:14:03 +02:00
})
2022-10-27 01:50:01 +02:00
const style = `width: ${options.width * options.scaling}mm; height: ${
options.height * options.scaling
}mm;`
2022-09-12 20:14:03 +02:00
minimap.SetStyle(style)
minimap.AttachTo(options.divId)
})
}
/**
* Creates a base64-encoded PNG image
* @constructor
*/
2022-10-27 01:50:01 +02:00
public async CreatePng(format: "image"): Promise<string>
public async CreatePng(format: "blob"): Promise<Blob>
public async CreatePng(format: "image" | "blob"): Promise<string | Blob>
2022-09-12 20:14:03 +02:00
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
2022-09-14 12:18:51 +02:00
const dummyMode = this._options.dummyMode ?? false
2022-09-16 18:58:42 +02:00
return new Promise<string | Blob>((resolve, reject) => {
2022-09-12 20:14:03 +02:00
// Next: we prepare the features. Only fully contained features are shown
minimap.leafletMap.addCallbackAndRunD(async (leaflet) => {
// Ping the featurepipeline to download what is needed
2022-09-14 12:18:51 +02:00
if (dummyMode) {
console.warn("Dummy mode is active - not loading map layers")
} else {
2022-10-27 01:50:01 +02:00
const bounds = BBox.fromLeafletBounds(
leaflet.getBounds().pad(0.1).pad(-state.layoutToUse.widenFactor)
)
2022-09-14 12:18:51 +02:00
state.currentBounds.setData(bounds)
2022-10-27 01:50:01 +02:00
if (!state.featurePipeline.sufficientlyZoomed.data) {
2022-09-15 14:17:39 +02:00
console.warn("Not sufficiently zoomed!")
}
2022-09-12 20:14:03 +02:00
2022-09-14 12:18:51 +02:00
if (state.featurePipeline.runningQuery.data) {
// A query is running!
// Let's wait for it to complete
console.log("Waiting for the query to complete")
2022-10-27 01:50:01 +02:00
await state.featurePipeline.runningQuery.AsPromise(
(isRunning) => !isRunning
)
2022-09-14 12:18:51 +02:00
console.log("Query has completeted!")
}
2022-09-12 20:14:03 +02:00
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,
})
})
2022-09-16 18:58:42 +02:00
await Utils.waitFor(2000)
2022-09-14 12:18:51 +02:00
}
2022-10-27 01:50:01 +02:00
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)
})
2022-09-12 20:14:03 +02:00
})
2022-09-14 12:18:51 +02:00
2022-09-12 20:14:03 +02:00
state.AddAllOverlaysToMap(minimap.leafletMap)
})
}
}