geosphere-maps/app/[locale]/print.tsx

275 lines
8.2 KiB
TypeScript

import { useRef, useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import MapView from '@arcgis/core/views/MapView';
import esriConfig from '@arcgis/core/config.js';
import * as print from '@arcgis/core/rest/print.js';
import PrintParameters from '@arcgis/core/rest/support/PrintParameters.js';
import PrintTemplate from '@arcgis/core/rest/support/PrintTemplate.js';
import * as reactiveUtils from '@arcgis/core/core/reactiveUtils.js';
import Point from '@arcgis/core/geometry/Point.js';
// set assets path for ArcGIS Maps SDK widgets
esriConfig.assetsPath = './assets';
// set local assets for Calcite components
import { setAssetPath } from '@esri/calcite-components/dist/components';
setAssetPath(window.location.href);
// import Calcite components
import '@esri/calcite-components/dist/components/calcite-button';
import '@esri/calcite-components/dist/components/calcite-panel';
import '@esri/calcite-components/dist/components/calcite-input-text';
import '@esri/calcite-components/dist/components/calcite-label';
import '@esri/calcite-components/dist/components/calcite-select';
import '@esri/calcite-components/dist/components/calcite-option';
import '@esri/calcite-components/dist/components/calcite-progress';
import {
CalciteButton,
CalcitePanel,
CalciteInputText,
CalciteLabel,
CalciteSelect,
CalciteOption,
CalciteProgress,
} from '@esri/calcite-components-react';
// helper function to clamp coordinates to visible area
function clamp(value: number, from: number, to: number) {
return value < from ? from : value > to ? to : value;
}
// print service URL
const printURL = 'https://gis.geosphere.at/maps/rest/services/tools/printing/GPServer/Export%20Web%20Map';
// available scales
const scales = [10000, 25000, 50000, 100000, 200000, 500000, 1000000, 3500000];
// available formats
interface Format {
[formatName: string]: number[];
}
// dimensions width and height in mm
const formats: Format = {
'A4 Hochformat': [190, 265],
'A4 Hochformat mit Legende': [190, 225],
'A4 Querformat': [277, 178],
'A4 Querformat mit Legende': [277, 140],
'A3 Hochformat': [277, 385],
'A3 Hochformat mit Legende': [277, 335],
'A3 Querformat': [400, 264],
'A3 Querformat mit Legende': [400, 215],
};
export default function Print({ view, maskRoot }: { view: MapView; maskRoot: HTMLDivElement }) {
const { t } = useTranslation();
const [title, setTitle] = useState<string>('GeoSphere Austria');
const [format, setFormat] = useState<string>(Object.keys(formats)[0]);
const [scale, setScale] = useState<number>(scales[0]);
const [printing, setPrinting] = useState<boolean>(false);
const rendered = useRef<boolean>(false);
const currentScale = useRef<number>();
currentScale.current = scale;
const currentFormat = useRef<string>();
currentFormat.current = format;
const controllerRef = useRef<AbortController | null>(null);
const handlePrint = () => {
setPrinting(true);
const template = new PrintTemplate({
layout: format,
format: 'pdf',
layoutOptions: {
titleText: title,
scalebarUnit: 'Kilometers',
customTextElements: [],
},
exportOptions: {
dpi: 96,
},
scalePreserved: true,
outScale: scale,
});
const params = new PrintParameters({
view,
template,
});
controllerRef.current = new AbortController();
print.execute(printURL, params, { signal: controllerRef.current.signal }).then(printResult).catch(printError);
};
function printResult(result: any) {
const filename = 'GeoSphere_Maps_Print.pdf';
fetch(result.url)
.then((res) => res.blob())
.then((blob) => {
const element = document.createElement('a');
element.setAttribute('href', URL.createObjectURL(blob));
element.setAttribute('download', filename);
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
setPrinting(false);
});
}
function printError(err: any) {
if (err.name === 'AbortError') {
// console.log('Request aborted');
} else {
console.error('Error encountered: ', err);
}
}
const handleTitleChange = (event: any) => {
setTitle(event.target.value);
};
const handleFormatChange = (event: any) => {
const newFormat = event.target.value;
setFormat(newFormat);
updatePreview(scale, newFormat);
};
const handleScaleChange = (event: any) => {
const newScale = parseInt(event.target.value);
setScale(newScale);
updatePreview(newScale, format);
};
const updatePreview = (newScale: number, newFormat: string) => {
const width = (formats[newFormat][0] * newScale) / 1000;
const height = (formats[newFormat][1] * newScale) / 1000;
setMask(width, height);
};
// set the mask for print preview
const setMask = (width: number, height: number) => {
const center = view.center;
if (center) {
const xmin = center.x - width / 2;
const xmax = center.x + width / 2;
const ymin = center.y - height / 2;
const ymax = center.y + height / 2;
const upperLeft = view.toScreen(
new Point({
x: xmin,
y: ymax,
spatialReference: view.spatialReference,
})
);
const lowerRight = view.toScreen(
new Point({
x: xmax,
y: ymin,
spatialReference: view.spatialReference,
})
);
const left = clamp(Math.round(upperLeft.x), 0, view.width);
const top = clamp(Math.round(upperLeft.y), 0, view.height);
const maskWidth = clamp(Math.round(lowerRight.x - upperLeft.x), 0, view.width);
const maskHeight = clamp(Math.round(lowerRight.y - upperLeft.y), 0, view.height);
if (maskRoot) {
maskRoot.style.left = left + 'px';
maskRoot.style.top = top + 'px';
maskRoot.style.width = maskWidth + 'px';
maskRoot.style.height = maskHeight + 'px';
}
}
};
useEffect(() => {
if (currentScale.current && currentFormat.current) {
updatePreview(currentScale.current, currentFormat.current);
}
let handle: any;
if (!rendered.current) {
handle = reactiveUtils.watch(
() => view?.extent,
() => {
if (currentScale.current && currentFormat.current) {
updatePreview(currentScale.current, currentFormat.current);
}
}
);
}
rendered.current = true;
return () => {
handle?.remove();
};
});
const handleAbort = () => {
if (controllerRef.current) {
controllerRef.current.abort();
setPrinting(false);
}
};
return (
<CalcitePanel heading={t('print.sub-heading')} className="px-3">
<div>{printing && <CalciteProgress type="indeterminate"></CalciteProgress>}</div>
<CalciteLabel className="mt-5 mx-5">
{t('print.titleLabel')}
<CalciteInputText
placeholder={t('print.titleLabel')}
onCalciteInputTextInput={handleTitleChange}
className="mx-0"
></CalciteInputText>
</CalciteLabel>
<CalciteLabel className="mx-5">
{t('print.formatLabel')}
<CalciteSelect label="format" onCalciteSelectChange={handleFormatChange}>
{Object.keys(formats).map((formatName) => {
return (
<CalciteOption key={`${formatName}`} value={`${formatName}`}>
{t(`print.formats.${formatName}`)}
</CalciteOption>
);
})}
</CalciteSelect>
</CalciteLabel>
<CalciteLabel className="mx-5">
{t('print.scaleLabel')}
<CalciteSelect label="scale" onCalciteSelectChange={handleScaleChange}>
{scales.map((num) => (
<CalciteOption value={`${num}`} key={num}>{`1:${num
.toString()
.match(/(\d+?)(?=(\d{3})+(?!\d)|$)/g)
?.join('.')}`}</CalciteOption>
))}
</CalciteSelect>
</CalciteLabel>
{!printing ? (
<CalciteButton width="half" slot="footer" onClick={handlePrint}>
{t('print.button.print')}
</CalciteButton>
) : (
<>
<CalciteButton width="half" slot="footer" onClick={handleAbort}>
{t('print.button.cancel')}
</CalciteButton>
</>
)}
</CalcitePanel>
);
}