273 lines
8.2 KiB
TypeScript
273 lines
8.2 KiB
TypeScript
import { useRef, useState, useEffect, RefObject } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
import initI18n from './i18n';
|
|
|
|
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],
|
|
};
|
|
|
|
// load localized strings
|
|
const match = location.pathname.match(/\/(\w+)/);
|
|
if (match && match.length > 1) {
|
|
const locale = match[1];
|
|
initI18n(locale);
|
|
}
|
|
|
|
export default function Print({ view, mask }: { view: MapView; mask: RefObject<HTMLDivElement> | null }) {
|
|
const [title, setTitle] = useState('GeoSphere Austria');
|
|
const [format, setFormat] = useState<string>(Object.keys(formats)[0]);
|
|
const [scale, setScale] = useState<number>(10000);
|
|
const [printing, setPrinting] = useState<boolean>(false);
|
|
const { t } = useTranslation();
|
|
|
|
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: 98,
|
|
},
|
|
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);
|
|
};
|
|
|
|
const setMask = (width: number, height: number) => {
|
|
const center = view.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 (mask && mask.current) {
|
|
mask.current.classList.remove('hidden');
|
|
mask.current.style.left = left + 'px';
|
|
mask.current.style.top = top + 'px';
|
|
mask.current.style.width = maskWidth + 'px';
|
|
mask.current.style.height = maskHeight + 'px';
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (currentScale.current && currentFormat.current) {
|
|
updatePreview(currentScale.current, currentFormat.current);
|
|
}
|
|
|
|
const handle = reactiveUtils.watch(
|
|
() => view?.extent,
|
|
() => {
|
|
if (currentScale.current && currentFormat.current) {
|
|
updatePreview(currentScale.current, currentFormat.current);
|
|
}
|
|
}
|
|
);
|
|
|
|
return () => {
|
|
handle.remove();
|
|
};
|
|
}, []);
|
|
|
|
const handleAbort = () => {
|
|
if (controllerRef.current) {
|
|
controllerRef.current.abort();
|
|
setPrinting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<CalcitePanel heading={t('heading')} className="px-3 w-80">
|
|
<div>{printing && <CalciteProgress type="indeterminate"></CalciteProgress>}</div>
|
|
<CalciteLabel className="mt-5 mx-5">
|
|
{t('titleLabel')}
|
|
<CalciteInputText
|
|
placeholder={t('titleLabel')}
|
|
onCalciteInputTextInput={handleTitleChange}
|
|
className="mx-0"
|
|
></CalciteInputText>
|
|
</CalciteLabel>
|
|
<CalciteLabel className="mx-5">
|
|
{t('formatLabel')}
|
|
<CalciteSelect label="format" onCalciteSelectChange={handleFormatChange}>
|
|
{Object.keys(formats).map((formatName) => {
|
|
return (
|
|
<CalciteOption key={`${formatName}`} value={`${formatName}`}>
|
|
{t(`formats.${formatName}`)}
|
|
</CalciteOption>
|
|
);
|
|
})}
|
|
</CalciteSelect>
|
|
</CalciteLabel>
|
|
|
|
<CalciteLabel className="mx-5">
|
|
{t('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('button.print')}
|
|
</CalciteButton>
|
|
) : (
|
|
<>
|
|
<CalciteButton width="half" slot="footer" onClick={handleAbort}>
|
|
{t('button.cancel')}
|
|
</CalciteButton>
|
|
</>
|
|
)}
|
|
</CalcitePanel>
|
|
);
|
|
}
|