@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
159 lines (158 loc) • 6.62 kB
JavaScript
import PrintMaskLayer from './printMaskLayer.js';
import GeoConsts from '../../../tools/geoconsts.js';
import { getOlayerByName } from '../../../tools/utils/olutils.js';
import { unByKey } from 'ol/Observable.js';
const PRINT_MASK_LAYER_NAME = 'PrintMask';
/**
* Independent manager to display a mask layer adapted to the print.
* Listen to print events to auto setup.
*/
class PrintMaskManager {
stateManager;
printMaskLayer = new PrintMaskLayer({ name: PRINT_MASK_LAYER_NAME });
map;
eventsCallbacks = [];
olEventsKeys = [];
possibleScales = [];
scaleManuallySelected = false;
constructor(map, stateManager) {
this.map = map;
this.stateManager = stateManager;
this.printMaskLayer.setGetScaleFn(this.getScaleFn.bind(this));
this.registerEvents();
}
get state() {
return this.stateManager.state;
}
destroy() {
this.stateManager.unsubscribe(this.eventsCallbacks);
this.eventsCallbacks.length = 0;
unByKey(this.olEventsKeys);
this.olEventsKeys.length = 0;
this.state.print.maskVisible = false;
this.onPrintMaskVisibleChanged();
}
/**
* Sets the possible scales to fit the mask/view to.
*/
setPossibleScales(scales) {
this.possibleScales = [...scales].sort((v1, v2) => v2 - v1).reverse();
}
/**
* Sets the resolution of the map view to the optimal resolution for a given scale.
*/
zoomToScale(scale) {
const mapSize = this.map.getSize() ?? [0, 0];
const printMapSize = this.state.print.pageSize ?? [0, 0];
const resolution = PrintMaskManager.getOptimalResolution(mapSize, printMapSize, scale);
const constraintRes = this.map.getView().getConstraints().resolution(resolution, 1, mapSize, undefined);
// Set the resolution (this emits the change:resolution event).
this.map.getView().setResolution(constraintRes);
// After the change:resolution event, force the map to render at the next key frame with
// scaleManuallySelected to true.
this.map.render();
this.scaleManuallySelected = true;
}
/**
* Register the events for:
* - printing pageSize and mask visibility changes.
* - set scaleManuallySelected to false when the resolution changes.
*/
registerEvents() {
this.eventsCallbacks.push(...[
this.stateManager.subscribe('print.pageSize', () => this.onPrintFormatChanged()),
this.stateManager.subscribe('print.maskVisible', () => this.onPrintMaskVisibleChanged())
]);
this.olEventsKeys.push(this.map.getView().on('change:resolution', () => {
this.scaleManuallySelected = false;
}));
}
/**
* Sets the print.scale value.
* Calculate the optimal scale for printing based on the map's current frame state and the possible scales.
* @returns the optimal scale.
* @private
*/
getScaleFn(frameState) {
// BGE debounce this could be great here
const mapSize = frameState.size;
const viewResolution = frameState.viewState.resolution;
console.assert(this.state.print.pageSize !== null, 'Can not get optimal scale without print pageSize');
const pageSize = this.state.print.pageSize ?? [0, 0];
let optimalScale;
if (this.scaleManuallySelected) {
optimalScale = this.state.print.scale ?? 10000;
}
else {
optimalScale = PrintMaskManager.getOptimalScale(mapSize, viewResolution, pageSize, this.possibleScales);
}
this.state.print.scale = optimalScale;
return this.state.print.scale;
}
/**
* Display the mask or hide it.
* @private
*/
onPrintMaskVisibleChanged() {
const isLayerInMap = getOlayerByName(this.map, PRINT_MASK_LAYER_NAME);
if (this.state.print.maskVisible && !isLayerInMap) {
this.map.addLayer(this.printMaskLayer);
return;
}
if (!this.state.print.maskVisible && isLayerInMap) {
this.map.removeLayer(this.printMaskLayer);
}
}
/**
* Update the size (width, height), in pixels of the mask.
* @private
*/
onPrintFormatChanged() {
if (!this.state.print.pageSize) {
return;
}
this.printMaskLayer.updateSize(this.state.print.pageSize);
this.renderMask();
}
/**
* Render the mask layer again.
* @private
*/
renderMask() {
if (this.state.print.maskVisible) {
this.printMaskLayer.changed();
}
}
/**
* Calculates the optimal scale for printing a map based on the map size, resolution, desired print size, and available map scales.
* @param mapSize - The size of the map in meters. Expressed as an array [width, height].
* @param mapResolution - The resolution of the map in meters per pixel.
* @param printMapSize - The desired size of the printed map in inches. Expressed as an array [width, height].
* @param mapScales - The available map scales.
* @returns The optimal scale for printing the map or -1 if any.
*/
static getOptimalScale(mapSize, mapResolution, printMapSize, mapScales) {
const mapWidth = mapSize[0] * mapResolution;
const mapHeight = mapSize[1] * mapResolution;
const scaleWidth = (mapWidth * GeoConsts.INCHES_PER_METER * GeoConsts.PRINT_DOTS_PER_INCH) / printMapSize[0];
const scaleHeight = (mapHeight * GeoConsts.INCHES_PER_METER * GeoConsts.PRINT_DOTS_PER_INCH) / printMapSize[1];
const scale = Math.min(scaleWidth, scaleHeight);
return mapScales.reduce((optimal, currentScale) => {
return scale > currentScale ? currentScale : optimal;
}, -1);
}
/**
* Calculates the optimal resolution for printing a map based on the provided parameters.
* @param mapSize - The size of the map in meters.
* @param printMapSize - The size of the map to be printed in dots.
* @param scale - The scale at which the map should be printed.
* @returns The optimal resolution for printing the map.
*/
static getOptimalResolution = (mapSize, printMapSize, scale) => {
const dotsPerMeter = GeoConsts.PRINT_DOTS_PER_INCH * GeoConsts.INCHES_PER_METER;
const resolutionX = (printMapSize[0] * scale) / (dotsPerMeter * mapSize[0]);
const resolutionY = (printMapSize[1] * scale) / (dotsPerMeter * mapSize[1]);
return Math.max(resolutionX, resolutionY);
};
}
export default PrintMaskManager;