From c0462da4c87eaec98e0db193583404166e75e589 Mon Sep 17 00:00:00 2001 From: Benjamin Gerber Date: Mon, 12 Feb 2024 18:07:31 +0100 Subject: [PATCH] Add wms image raw implementation --- CHANGES.md | 3 + demo/demo.js | 4 +- src/MFPEncoder.ts | 232 ++++++++++++++++++++++++++++++++-------------- src/types.ts | 15 +++ test.js | 3 +- 5 files changed, 182 insertions(+), 75 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6a52ca4..875f17d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,9 @@ ## 0.2.4 - Move createReport to utils. +- MFPEncoder has now a setPrintResolution method. printResolution is not anymore + passed as an option. +- Add **raw** encode support for WMS and Tile WMS layers. ## 0.2.3 - Add utility functions. diff --git a/demo/demo.js b/demo/demo.js index 9be5fe3..3928709 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -27,13 +27,13 @@ document.querySelector('#print').addEventListener('click', async () => { specEl.innerHTML = reportEl.innerHTML = resultEl.innerHTML = ''; const encoder = new MFPEncoder(MFP_URL); const customizer = new BaseCustomizer([0, 0, 10000, 10000]); + encoder.setPrintResolution(map.getView().getResolution()); /** * @type {MFPMap} */ const mapSpec = await encoder.encodeMap({ map, - scale: 1, - printResolution: 96, + scale: 10000, dpi: 254, customizer: customizer, }); diff --git a/src/MFPEncoder.ts b/src/MFPEncoder.ts index 005d7ad..41a0a00 100644 --- a/src/MFPEncoder.ts +++ b/src/MFPEncoder.ts @@ -3,17 +3,27 @@ import {drawFeaturesToContext, createCoordinateToPixelTransform} from './mvtUtil import TileLayer from 'ol/layer/Tile.js'; import WMTSSource from 'ol/source/WMTS.js'; +import TileWMSSource from 'ol/source/TileWMS.js'; import OSMSource from 'ol/source/OSM.js'; import {getWidth as getExtentWidth, getHeight as getExtentHeight} from 'ol/extent.js'; import BaseCustomizer from './BaseCustomizer'; import type Map from 'ol/Map.js'; -import type {MFPImageLayer, MFPLayer, MFPMap, MFPOSMLayer, MFPWmtsLayer} from './types'; +import type { + MFPImageLayer, + MFPLayer, + MFPMap, + MFPOSMLayer, + MFPWmtsLayer, + MFPVectorLayer, + MFPWmsLayer, +} from './types'; import type WMTS from 'ol/source/WMTS.js'; - import type {Geometry} from 'ol/geom.js'; import type {State} from 'ol/layer/Layer.js'; +import ImageLayer from 'ol/layer/Image.js'; +import ImageWMSSource from 'ol/source/ImageWMS.js'; import {toDegrees} from 'ol/math.js'; import VectorTileLayer from 'ol/layer/VectorTile.js'; import VectorLayer from 'ol/layer/Vector.js'; @@ -26,7 +36,6 @@ import VectorContext from 'ol/render/VectorContext'; export interface EncodeMapOptions { map: Map; scale: number; - printResolution: number; dpi: number; customizer: BaseCustomizer; } @@ -37,6 +46,7 @@ export interface EncodeMapOptions { export default class MFPBaseEncoder { readonly url: string; private scratchCanvas: HTMLCanvasElement = document.createElement('canvas'); + private printResolution = 1; /** * @@ -46,6 +56,14 @@ export default class MFPBaseEncoder { this.url = printUrl; } + /** + * Set the current print resolution. + * This offer comes from the ol view. + */ + setPrintResolution(resolution: number) { + this.printResolution = resolution; + } + /** * * @param options @@ -57,7 +75,7 @@ export default class MFPBaseEncoder { const projection = view.getProjection().getCode(); const rotation = toDegrees(view.getRotation()); const mapLayerGroup = options.map.getLayerGroup(); - const layers = await this.encodeLayerGroup(mapLayerGroup, options.printResolution, options.customizer); + const layers = await this.encodeLayerGroup(mapLayerGroup, options.customizer); return { center, @@ -72,15 +90,10 @@ export default class MFPBaseEncoder { /** * * @param layerGroup The top level layer group of a map - * @param printResolution * @param customizer * @return a list of Mapfish print layer specs */ - async encodeLayerGroup( - layerGroup: LayerGroup, - printResolution: number, - customizer: BaseCustomizer, - ): Promise { + async encodeLayerGroup(layerGroup: LayerGroup, customizer: BaseCustomizer): Promise { const layerStates = layerGroup .getLayerStatesArray() .filter(customizer.layerFilter) @@ -89,8 +102,7 @@ export default class MFPBaseEncoder { const layers: MFPLayer[] = []; for (const layerState of layerStates) { - console.assert(printResolution !== undefined); - const spec = await this.encodeLayerState(layerState, printResolution, customizer); + const spec = await this.encodeLayerState(layerState, customizer); if (spec) { if (Array.isArray(spec)) { layers.push(...spec); @@ -105,83 +117,100 @@ export default class MFPBaseEncoder { /** * Encodes a given OpenLayers layerState to Mapfish print format. * @param layerState - * @param printResolution * @param customizer * @return a spec fragment */ async encodeLayerState( layerState: State, - printResolution: number, customizer: BaseCustomizer, ): Promise { if ( !layerState.visible || - printResolution < layerState.minResolution || - printResolution >= layerState.maxResolution + this.printResolution < layerState.minResolution || + this.printResolution >= layerState.maxResolution ) { return null; } const layer = layerState.layer; - if (layer instanceof VectorTileLayer) { - return await this.encodeMVTLayerState(layerState, printResolution, customizer); + if (layer instanceof ImageLayer) { + return this.encodeImageLayerState(layerState, customizer); + } + + if (layer instanceof VectorLayer) { + const encoded = new VectorEncoder(layerState, customizer).encodeVectorLayer(this.printResolution)!; + this.addRenderAsSVG(layerState, encoded); + return encoded; } if (layer instanceof TileLayer) { return this.encodeTileLayerState(layerState, customizer); - } else if (layer instanceof VectorLayer) { - const encoded = new VectorEncoder(layerState, customizer).encodeVectorLayer(printResolution)!; - const renderAsSvg = layer.get('renderAsSvg'); - if (renderAsSvg !== undefined) { - encoded.renderAsSvg = renderAsSvg; - } - return encoded; + } + + if (layer instanceof VectorTileLayer) { + return await this.encodeMVTLayerState(layerState, customizer); } return null; } /** - * - * @param layerState An MVT layer state - * @param printResolution - * @param customizer - * @return a spec fragment + * Get "renderAsSvg" to the encoded object if it exists in the layer. */ - async encodeMVTLayerState( - layerState: State, - printResolution: number, - customizer: BaseCustomizer, - ): Promise { - const layer = layerState.layer as VectorTileLayer; - const {MVTEncoder} = await import('@geoblocks/print'); - const encoder = new MVTEncoder(); - const printExtent = customizer.getPrintExtent(); - const width = getExtentWidth(printExtent) / printResolution; - const height = getExtentHeight(printExtent) / printResolution; - const canvasSize: [number, number] = [width, height]; - const printOptions = { - layer, - printExtent: customizer.getPrintExtent(), - tileResolution: printResolution, - styleResolution: printResolution, - canvasSize: canvasSize, + addRenderAsSVG(layerState: State, encoded: MFPVectorLayer) { + const renderAsSvg = layerState.layer.get('renderAsSvg'); + if (renderAsSvg !== undefined) { + encoded.renderAsSvg = renderAsSvg; + } + } + + /** + * @returns An Encoded WMS Image layer from an Image Layer (high level method). + */ + encodeImageLayerState(layerState: State, customizer: BaseCustomizer): MFPWmsLayer | null { + const layer = layerState.layer; + if (!(layer instanceof ImageLayer)) { + console.assert(layer instanceof ImageLayer); + } + const source = layer.getSource(); + if (source instanceof ImageWMSSource) { + return this.encodeImageWmsLayerState(layerState, customizer); + } + return null; + } + + /** + * @returns An Encoded WMS Image layer from an Image WMS Source (high level method). + */ + encodeImageWmsLayerState(layerState: State, customizer: BaseCustomizer) { + const layer = layerState.layer; + const source = layer.getSource() as ImageWMSSource; + console.assert(layer instanceof ImageWMSSource); + const url = source.getUrl(); + if (url !== undefined) { + return this.encodeWmsLayerState(layerState, url, source.getParams(), customizer); + } + return null; + } + + /** + * @returns An Encoded WMS Image layer from an Image WMS Source. + */ + encodeWmsLayerState(layerState: State, url: string, params: any, customizer: BaseCustomizer): MFPWmsLayer { + const layer = layerState.layer; + return { + name: layer.get('name'), + baseURL: url, + imageFormat: 'image/png', + layers: [''], + customParams: {}, + serverType: 'mapserver', + type: 'wms', + opacity: layer.getOpacity(), + version: params.VERSION, + useNativeAngle: true, + styles: [''], }; - const results = await encoder.encodeMVTLayer(printOptions); - return results - .filter((resut) => resut.baseURL.length > 6) - .map( - (result) => - Object.assign( - { - type: 'image', - name: layer.get('name'), - opacity: 1, - imageFormat: 'image/png', - }, - result, - ) as MFPLayer, - ); } /** @@ -190,17 +219,39 @@ export default class MFPBaseEncoder { * @param customizer * @return a spec fragment */ - encodeTileLayerState(layerState: State, customizer: BaseCustomizer): MFPOSMLayer | MFPWmtsLayer { + encodeTileLayerState( + layerState: State, + customizer: BaseCustomizer, + ): MFPOSMLayer | MFPWmtsLayer | MFPWmsLayer | null { const layer = layerState.layer; console.assert(layer instanceof TileLayer); const source = layer.getSource(); if (source instanceof WMTSSource) { - return this.encodeTileWmtsLayer(layerState, customizer); - } else if (source instanceof OSMSource) { + return this.encodeTileWmtsLayerState(layerState, customizer); + } + if (source instanceof TileWMSSource) { + return this.encodeTileWmsLayerState(layerState, customizer); + } + if (source instanceof OSMSource) { return this.encodeOSMLayerState(layerState, customizer); - } else { - return null; } + return null; + } + + /** + * Encodes a tiled WMS layerState as a MFPWmsLayer + * @param layerState + * @param customizer + * @return a spec fragment + */ + encodeTileWmsLayerState(layerState: State, customizer: BaseCustomizer): MFPWmsLayer { + const layer = layerState.layer; + console.assert(layer instanceof TileLayer); + const source = layer.getSource() as TileWMSSource; + console.assert(layer instanceof TileWMSSource); + const urls = source.getUrls(); + console.assert(!!urls); + return this.encodeWmsLayerState(layerState, urls[0], source.getParams(), customizer); } /** @@ -226,7 +277,7 @@ export default class MFPBaseEncoder { * @param customizer * @return a spec fragment */ - encodeTileWmtsLayer(layerState: State, customizer: BaseCustomizer): MFPWmtsLayer { + encodeTileWmtsLayerState(layerState: State, customizer: BaseCustomizer): MFPWmtsLayer { const layer = layerState.layer; console.assert(layer instanceof TileLayer); const source = layer.getSource()! as WMTS; @@ -254,6 +305,46 @@ export default class MFPBaseEncoder { return wmtsLayer; } + /** + * @param layerState An MVT layer state + * @param customizer + * @return a spec fragment + */ + async encodeMVTLayerState( + layerState: State, + customizer: BaseCustomizer, + ): Promise { + const layer = layerState.layer as VectorTileLayer; + const {MVTEncoder} = await import('@geoblocks/print'); + const encoder = new MVTEncoder(); + const printExtent = customizer.getPrintExtent(); + const width = getExtentWidth(printExtent) / this.printResolution; + const height = getExtentHeight(printExtent) / this.printResolution; + const canvasSize: [number, number] = [width, height]; + const printOptions = { + layer, + printExtent: customizer.getPrintExtent(), + tileResolution: this.printResolution, + styleResolution: this.printResolution, + canvasSize: canvasSize, + }; + const results = await encoder.encodeMVTLayer(printOptions); + return results + .filter((resut) => resut.baseURL.length > 6) + .map( + (result) => + Object.assign( + { + type: 'image', + name: layer.get('name'), + opacity: 1, + imageFormat: 'image/png', + }, + result, + ) as MFPLayer, + ); + } + /** * Encodes Image layerState. * @param layerState @@ -289,7 +380,7 @@ export default class MFPBaseEncoder { additionalDraw, ); - const spec: MFPImageLayer = { + return { type: 'image', extent: printExtent, imageFormat: 'image/png', // this is the target image format in the mapfish-print @@ -297,6 +388,5 @@ export default class MFPBaseEncoder { name: layer.get('name'), baseURL: asOpacity(this.scratchCanvas, layer.getOpacity()).toDataURL('PNG'), }; - return spec; } } diff --git a/src/types.ts b/src/types.ts index 81ba338..cb4ba89 100644 --- a/src/types.ts +++ b/src/types.ts @@ -96,6 +96,21 @@ export interface MFPWmtsLayer extends MFPLayer { version: string; } +export interface MFPWmsLayer extends MFPLayer { + type: 'wms'; + baseURL: string; + imageFormat: string; + layers: string[]; + customParams: Record; + /** The server type ("mapserver", "geoserver" or "qgisserver"). */ + serverType: string; + opacity: number; + version: string; + styles: string[]; + /** For GeoServer, and MapServer, ask the map server to do the rotation. */ + useNativeAngle: boolean; +} + export interface MFPImageLayer extends MFPLayer { type: 'image'; extent: number[]; diff --git a/test.js b/test.js index 134ac76..ebccb78 100644 --- a/test.js +++ b/test.js @@ -18,10 +18,10 @@ test('Empty map', async (t) => { }), }); map.getView(); + encoder.setPrintResolution(map.getView().getResolution()); const result = await encoder.encodeMap({ map, scale: 1, - printResolution: 96, dpi: 300, customizer: customizer, }); @@ -56,7 +56,6 @@ test('OSM map', async (t) => { const spec = await encoder.encodeMap({ map, scale: 1, - printResolution: 96, dpi: 254, customizer: customizer, });