Work on Popups
This commit is contained in:
parent
a810f87461
commit
0d509d4e00
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||
}
|
|
@ -10,8 +10,8 @@ import MapImageLayer from '@arcgis/core/layers/MapImageLayer';
|
|||
import Sublayer from '@arcgis/core/layers/support/Sublayer';
|
||||
import ButtonMenuItem from '@arcgis/core/widgets/FeatureTable/Grid/support/ButtonMenuItem';
|
||||
|
||||
export default function Layers({ view, tableRoot }: { view: MapView; tableRoot: HTMLDivElement }) {
|
||||
const htmlDiv = useRef<HTMLDivElement>(null);
|
||||
export default function Layers({ view, tableDiv }: { view: MapView; tableDiv: HTMLDivElement }) {
|
||||
const layerListDiv = useRef<HTMLDivElement | null>(null);
|
||||
const featureTable = useRef<FeatureTable | null>(null);
|
||||
const rendered = useRef<boolean>(false);
|
||||
|
||||
|
@ -25,9 +25,9 @@ export default function Layers({ view, tableRoot }: { view: MapView; tableRoot:
|
|||
const tableContainer = document.createElement('div');
|
||||
tableContainer.className = 'h-full w-full';
|
||||
|
||||
if (tableRoot) {
|
||||
tableRoot.classList.remove('hidden');
|
||||
tableRoot.append(tableContainer);
|
||||
if (tableDiv) {
|
||||
tableDiv.classList.remove('hidden');
|
||||
tableDiv.append(tableContainer);
|
||||
}
|
||||
|
||||
const featureLayer = await layer.createFeatureLayer();
|
||||
|
@ -43,8 +43,8 @@ export default function Layers({ view, tableRoot }: { view: MapView; tableRoot:
|
|||
iconClass: 'esri-icon-close',
|
||||
clickFunction: function () {
|
||||
featureTable.current?.destroy();
|
||||
if (tableRoot) {
|
||||
tableRoot.classList.add('hidden');
|
||||
if (tableDiv) {
|
||||
tableDiv.classList.add('hidden');
|
||||
}
|
||||
},
|
||||
} as unknown as ButtonMenuItem,
|
||||
|
@ -53,13 +53,13 @@ export default function Layers({ view, tableRoot }: { view: MapView; tableRoot:
|
|||
});
|
||||
};
|
||||
|
||||
if (htmlDiv.current) {
|
||||
if (layerListDiv.current) {
|
||||
const arcGISAPIWidgetContainer = document.createElement('div');
|
||||
htmlDiv.current.append(arcGISAPIWidgetContainer);
|
||||
layerListDiv.current.append(arcGISAPIWidgetContainer);
|
||||
|
||||
const layerList = new LayerList({
|
||||
view,
|
||||
container: htmlDiv.current,
|
||||
container: layerListDiv.current,
|
||||
selectionEnabled: true,
|
||||
listItemCreatedFunction: async function (event) {
|
||||
const item = event.item;
|
||||
|
@ -139,9 +139,7 @@ export default function Layers({ view, tableRoot }: { view: MapView; tableRoot:
|
|||
}
|
||||
});
|
||||
}
|
||||
}, [t, tableDiv, view]);
|
||||
|
||||
return () => {};
|
||||
});
|
||||
|
||||
return <div ref={htmlDiv}></div>;
|
||||
return <div ref={layerListDiv}></div>;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { Root, createRoot } from 'react-dom/client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
|
@ -10,22 +12,24 @@ import WebMap from '@arcgis/core/WebMap';
|
|||
import esriConfig from '@arcgis/core/config';
|
||||
import ScaleBar from '@arcgis/core/widgets/ScaleBar';
|
||||
import Legend from '@arcgis/core/widgets/Legend';
|
||||
import VectorTileLayer from '@arcgis/core/layers/VectorTileLayer';
|
||||
import TileLayer from '@arcgis/core/layers/TileLayer';
|
||||
import Map from '@arcgis/core/Map.js';
|
||||
import Basemap from '@arcgis/core/Basemap';
|
||||
import * as intl from '@arcgis/core/intl';
|
||||
import TextContent from '@arcgis/core/popup/content/TextContent';
|
||||
import ExpressionContent from '@arcgis/core/popup/content/ExpressionContent';
|
||||
import ElementExpressionInfo from '@arcgis/core/popup/ElementExpressionInfo';
|
||||
import { watch } from '@arcgis/core/core/reactiveUtils';
|
||||
// @ts-ignore
|
||||
import { getColorsForRendererValues } from '@arcgis/core/renderers/support/utils';
|
||||
|
||||
// set asset path for ArcGIS Maps SDK widgets
|
||||
esriConfig.assetsPath = './assets';
|
||||
esriConfig.assetsPath = '/assets';
|
||||
|
||||
// ids of web map items in portal
|
||||
const webMapDEID = '7d0768f73d3e4be2b32c22274c600cb3';
|
||||
const webMapENID = 'dbf5532d06954c6a989d4f022de83f70';
|
||||
|
||||
// lazy load components
|
||||
const Print = dynamic(() => import('./print'));
|
||||
const Layers = dynamic(() => import('./layer-list'));
|
||||
const Print = dynamic(() => import('./print'));
|
||||
const Basemaps = dynamic(() => import('./basemap-list'));
|
||||
const Search = dynamic(() => import('./search'));
|
||||
|
||||
|
@ -48,20 +52,30 @@ import {
|
|||
CalciteAction,
|
||||
CalcitePanel,
|
||||
} from '@esri/calcite-components-react';
|
||||
import MapImageLayer from '@arcgis/core/layers/MapImageLayer';
|
||||
|
||||
export default function MapComponent({ locale }: { locale: string }) {
|
||||
const legendRoot = useRef<HTMLDivElement>(null);
|
||||
const maskRoot = useRef<HTMLDivElement>(null);
|
||||
const tableRoot = useRef<HTMLDivElement>(null);
|
||||
const maskRef = useRef<HTMLDivElement>(null);
|
||||
const tableRef = useRef<HTMLDivElement>(null);
|
||||
const mapView = useRef<MapView | null>(null);
|
||||
const previousId = useRef<string | null>(null);
|
||||
const mapRef = useRef<HTMLDivElement | null>(null);
|
||||
const layersRef = useRef<HTMLDivElement>(null);
|
||||
const layersRoot = useRef<Root | null>(null);
|
||||
const legendRef = useRef<HTMLDivElement>(null);
|
||||
const basemapsRef = useRef<HTMLDivElement>(null);
|
||||
const basemapsRoot = useRef<Root | null>(null);
|
||||
const printRef = useRef<HTMLDivElement>(null);
|
||||
const printRoot = useRef<Root | null>(null);
|
||||
const searchRef = useRef<HTMLDivElement>(document.createElement('div'));
|
||||
const searchRoot = useRef<Root | null>(null);
|
||||
|
||||
const [actionBarExpanded, setActionBarExpanded] = useState<boolean>(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!mapView.current) {
|
||||
if (mapRef.current) {
|
||||
// set locale for ArcGIS Maps SDK widgets
|
||||
intl.setLocale(locale);
|
||||
|
||||
|
@ -74,40 +88,16 @@ export default function MapComponent({ locale }: { locale: string }) {
|
|||
},
|
||||
});
|
||||
|
||||
const lightgrayBase = new VectorTileLayer({
|
||||
url: 'https://gis.geosphere.at/portal/sharing/rest/content/items/291da5eab3a0412593b66d384379f89f/resources/styles/root.json',
|
||||
opacity: 0.5,
|
||||
});
|
||||
const lightGrayReference = new VectorTileLayer({
|
||||
url: 'https://gis.geosphere.at/portal/sharing/rest/content/items/1768e8369a214dfab4e2167d5c5f2454/resources/styles/root.json',
|
||||
opacity: 1,
|
||||
});
|
||||
const worldHillshade = new TileLayer({
|
||||
url: 'https://services.arcgisonline.com/arcgis/rest/services/Elevation/World_Hillshade/MapServer',
|
||||
});
|
||||
const basemapEsri = new Basemap({
|
||||
baseLayers: [worldHillshade, lightgrayBase, lightGrayReference],
|
||||
title: 'Esri',
|
||||
thumbnailUrl:
|
||||
'https://gis.geosphere.at/portal/sharing/rest/content/items/3eb1510943be4f29ae01c01ce229d8ba/data',
|
||||
});
|
||||
|
||||
const map = new Map({
|
||||
basemap: basemapEsri,
|
||||
});
|
||||
|
||||
const view = new MapView({
|
||||
container: 'map-container',
|
||||
map: map,
|
||||
container: mapRef.current,
|
||||
map: webMap,
|
||||
padding: {
|
||||
left: 49,
|
||||
},
|
||||
popup: {
|
||||
dockOptions: {
|
||||
position: 'auto',
|
||||
breakpoint: {
|
||||
width: 5000,
|
||||
},
|
||||
position: 'top-right',
|
||||
breakpoint: false,
|
||||
},
|
||||
dockEnabled: true,
|
||||
},
|
||||
|
@ -120,17 +110,15 @@ export default function MapComponent({ locale }: { locale: string }) {
|
|||
wkid: 3857,
|
||||
},
|
||||
},
|
||||
ui: {
|
||||
components: ['attribution'],
|
||||
},
|
||||
popupEnabled: true,
|
||||
});
|
||||
|
||||
mapView.current = view;
|
||||
view.ui.empty('top-left');
|
||||
|
||||
webMap.load().then(() => {
|
||||
map.layers = webMap.layers;
|
||||
createRoot(document.createElement('div')).render(<Search view={view}></Search>);
|
||||
});
|
||||
|
||||
// add further map related UI components
|
||||
// add ScaleBar
|
||||
const scaleBar = new ScaleBar({
|
||||
view: view,
|
||||
unit: 'metric',
|
||||
|
@ -138,16 +126,124 @@ export default function MapComponent({ locale }: { locale: string }) {
|
|||
|
||||
view.ui.add([scaleBar], 'bottom-left');
|
||||
|
||||
if (legendRoot.current) {
|
||||
// render Legend component
|
||||
if (legendRef.current) {
|
||||
new Legend({
|
||||
view: view,
|
||||
container: legendRoot.current,
|
||||
container: legendRef.current,
|
||||
});
|
||||
}
|
||||
|
||||
// render Search component
|
||||
if (searchRef.current) {
|
||||
if (!searchRoot.current) {
|
||||
searchRoot.current = createRoot(searchRef.current);
|
||||
}
|
||||
|
||||
searchRoot.current.render(<Search view={view}></Search>);
|
||||
}
|
||||
|
||||
// render Layers component
|
||||
if (layersRef.current) {
|
||||
if (!layersRoot.current) {
|
||||
layersRoot.current = createRoot(layersRef.current);
|
||||
}
|
||||
|
||||
if (tableRef.current) {
|
||||
layersRoot.current.render(<Layers view={view} tableDiv={tableRef.current}></Layers>);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {};
|
||||
}, [locale]);
|
||||
// render Basemaps component
|
||||
if (basemapsRef.current) {
|
||||
if (!basemapsRoot.current) {
|
||||
basemapsRoot.current = createRoot(basemapsRef.current);
|
||||
}
|
||||
|
||||
basemapsRoot.current.render(<Basemaps view={mapView.current}></Basemaps>);
|
||||
}
|
||||
|
||||
// // render Print component
|
||||
if (printRef.current) {
|
||||
if (!printRoot.current) {
|
||||
printRoot.current = createRoot(printRef.current);
|
||||
}
|
||||
|
||||
if (maskRef.current) {
|
||||
printRoot.current.render(<Print view={mapView.current} maskDiv={maskRef.current}></Print>);
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => view.popup?.viewModel?.active,
|
||||
() => console.log(view.popup?.selectedFeature)
|
||||
);
|
||||
|
||||
// view.on('immediate-click', (event) => {
|
||||
// const mapImageLayer = view.map.layers.find((layer) => layer.title === 'Profilschnitte') as MapImageLayer;
|
||||
// if (mapImageLayer) {
|
||||
// mapImageLayer.allSublayers.forEach((sublayer) => {
|
||||
// if (sublayer.title === 'Bohrprofile') {
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// This function fires each time a LayerView is created
|
||||
// view.on('layerview-create', function (event) {
|
||||
// // The LayerView for the desired layer
|
||||
// if (event.layer.type === 'map-image') {
|
||||
// (event.layer as MapImageLayer).allSublayers.forEach((sublayer) => {
|
||||
// if (sublayer.title === 'Bohrprofile') {
|
||||
// let textElement = new TextContent();
|
||||
// textElement.text = 'Das ist nur ein Test.';
|
||||
// if (Array.isArray(sublayer.popupTemplate.content)) sublayer.popupTemplate.content.push(textElement);
|
||||
|
||||
// sublayer.load().then(async () => {
|
||||
// const renderer = sublayer.renderer;
|
||||
// const fieldToValueColorMap = await getColorsForRendererValues(renderer);
|
||||
|
||||
// let classBreaks: number[] = [];
|
||||
// let colors: object[] = [];
|
||||
// for (const [key, classBreaksToColorMap] of fieldToValueColorMap) {
|
||||
// for (const [classBreack, color] of classBreaksToColorMap) {
|
||||
// colors.push(color);
|
||||
// classBreaks.push(classBreack);
|
||||
// }
|
||||
|
||||
// // while (!stopIteration) {
|
||||
// // if ($feature['${key}'] > ${classBreaks}[index]) {
|
||||
// // stopIteration = true;
|
||||
// // color = ${colors}[index]
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// const expressionContent = new ExpressionContent({
|
||||
// expressionInfo: {
|
||||
// title: 'Legende',
|
||||
// expression: `
|
||||
// var stopIteration = true;
|
||||
// var index = 0;
|
||||
|
||||
// return {
|
||||
// type: "text",
|
||||
// text: "First class break for key ${key}: ${classBreaks[0]} and value: " + $feature['${key}']
|
||||
// }
|
||||
// `,
|
||||
// },
|
||||
// });
|
||||
|
||||
// if (Array.isArray(sublayer.popupTemplate.content)) {
|
||||
// sublayer.popupTemplate.content.push(expressionContent);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
}
|
||||
}, [locale, t]);
|
||||
|
||||
const handleCalciteActionBarToggle = () => {
|
||||
setActionBarExpanded(!actionBarExpanded);
|
||||
|
@ -155,13 +251,13 @@ export default function MapComponent({ locale }: { locale: string }) {
|
|||
mapView.current.padding = !actionBarExpanded ? { left: 150 } : { left: 49 };
|
||||
}
|
||||
|
||||
if (tableRoot.current) {
|
||||
if (tableRef.current) {
|
||||
if (!actionBarExpanded) {
|
||||
tableRoot.current.classList.add('left-40');
|
||||
tableRoot.current.classList.remove('left-14');
|
||||
tableRef.current.classList.add('left-40');
|
||||
tableRef.current.classList.remove('left-14');
|
||||
} else {
|
||||
tableRoot.current.classList.add('left-14');
|
||||
tableRoot.current.classList.remove('left-40');
|
||||
tableRef.current.classList.add('left-14');
|
||||
tableRef.current.classList.remove('left-40');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -180,7 +276,7 @@ export default function MapComponent({ locale }: { locale: string }) {
|
|||
}
|
||||
|
||||
if (previousId.current === 'print') {
|
||||
maskRoot.current?.classList.add('hidden');
|
||||
maskRef.current?.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,7 +286,7 @@ export default function MapComponent({ locale }: { locale: string }) {
|
|||
previousId.current = nextId;
|
||||
|
||||
if (nextId === 'print') {
|
||||
maskRoot.current?.classList.remove('hidden');
|
||||
maskRef.current?.classList.remove('hidden');
|
||||
}
|
||||
} else {
|
||||
previousId.current = null;
|
||||
|
@ -234,32 +330,30 @@ export default function MapComponent({ locale }: { locale: string }) {
|
|||
</CalciteActionBar>
|
||||
|
||||
<CalcitePanel data-panel-id="layers" heading={t('layers.title')} hidden>
|
||||
{mapView.current && tableRoot.current && (
|
||||
<Layers view={mapView.current} tableRoot={tableRoot.current}></Layers>
|
||||
)}
|
||||
<div ref={layersRef}></div>
|
||||
</CalcitePanel>
|
||||
|
||||
<CalcitePanel data-panel-id="basemaps" heading={t('basemaps.title')} hidden>
|
||||
{mapView.current && <Basemaps view={mapView.current}></Basemaps>}
|
||||
<div ref={basemapsRef}></div>
|
||||
</CalcitePanel>
|
||||
|
||||
<CalcitePanel data-panel-id="legend" heading={t('legend.title')} hidden>
|
||||
<div ref={legendRoot}></div>
|
||||
<div ref={legendRef}></div>
|
||||
</CalcitePanel>
|
||||
|
||||
<CalcitePanel data-panel-id="print" heading={t('print.heading')} hidden>
|
||||
{mapView.current && maskRoot.current && <Print view={mapView.current} maskRoot={maskRoot.current}></Print>}
|
||||
<div ref={printRef}></div>
|
||||
</CalcitePanel>
|
||||
|
||||
<CalcitePanel data-panel-id="info" heading="Info" hidden>
|
||||
<div id="info-container"></div>
|
||||
</CalcitePanel>
|
||||
</CalciteShellPanel>
|
||||
<div className="h-screen w-full" id="map-container">
|
||||
<div ref={tableRoot} className="hidden absolute left-14 bottom-5 h-1/3 right-2 border border-gray-400"></div>
|
||||
<div className="h-screen w-full" ref={mapRef}>
|
||||
<div ref={tableRef} className="hidden absolute left-14 bottom-5 h-1/3 right-2 border border-gray-400"></div>
|
||||
</div>
|
||||
<div
|
||||
ref={maskRoot}
|
||||
ref={maskRef}
|
||||
className="hidden absolute bg-red-300 border-2 border-red-600 opacity-50 pointer-events-none"
|
||||
></div>
|
||||
</CalciteShell>
|
||||
|
|
|
@ -63,7 +63,7 @@ const formats: Format = {
|
|||
'A3 Querformat mit Legende': [400, 215],
|
||||
};
|
||||
|
||||
export default function Print({ view, maskRoot }: { view: MapView; maskRoot: HTMLDivElement }) {
|
||||
export default function Print({ view, maskDiv }: { view: MapView; maskDiv: HTMLDivElement }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [title, setTitle] = useState<string>('GeoSphere Austria');
|
||||
|
@ -183,11 +183,11 @@ export default function Print({ view, maskRoot }: { view: MapView; maskRoot: HTM
|
|||
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';
|
||||
if (maskDiv) {
|
||||
maskDiv.style.left = left + 'px';
|
||||
maskDiv.style.top = top + 'px';
|
||||
maskDiv.style.width = maskWidth + 'px';
|
||||
maskDiv.style.height = maskHeight + 'px';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useRef, useState, useEffect } from 'react';
|
||||
import { useRef, useState, useEffect, MouseEventHandler } from 'react';
|
||||
import { Root, createRoot } from 'react-dom/client';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -40,14 +40,17 @@ import SimpleLineSymbol from '@arcgis/core/symbols/SimpleLineSymbol';
|
|||
import SimpleMarkerSymbol from '@arcgis/core/symbols/SimpleMarkerSymbol';
|
||||
import Sublayer from '@arcgis/core/layers/support/Sublayer';
|
||||
import PopupTemplate from '@arcgis/core/PopupTemplate';
|
||||
import { watch } from '@arcgis/core/core/reactiveUtils';
|
||||
|
||||
// create feaure layer from URL of data index layer
|
||||
const datenIndexURL = 'https://gis.geosphere.at/maps/rest/services/datenindex/raster_5000/MapServer/0';
|
||||
const datenIndexURL = 'https://gis.geosphere.at/maps/rest/services/datenindex/raster_1000/MapServer/0';
|
||||
const indexLayer = new FeatureLayer({
|
||||
url: datenIndexURL,
|
||||
title: 'Datenindex 1:5.000',
|
||||
opacity: 0,
|
||||
title: 'Datenindex 1:1.000',
|
||||
opacity: 1,
|
||||
legendEnabled: false,
|
||||
visible: false,
|
||||
listMode: 'hide',
|
||||
});
|
||||
|
||||
// custom type definitions
|
||||
|
@ -70,152 +73,30 @@ interface LayerToFeaturesMap {
|
|||
[key: string]: string[];
|
||||
}
|
||||
|
||||
// custom React component
|
||||
export default function SearchComponent({ view }: { view: MapView }) {
|
||||
const [currentTarget, setCurrentTarget] = useState<Graphic | null>(null);
|
||||
// create query from cellcode for 3x3 neighbourhood
|
||||
const createQueryFromCellcode = (cellcode: string) => {
|
||||
const { north, east }: any = cellcode.match(/N(?<north>\d+)E(?<east>\d+)/)?.groups;
|
||||
const northNumber = parseInt(north);
|
||||
const eastNumber = parseInt(east);
|
||||
const operations = [
|
||||
[1, -1],
|
||||
[1, 0],
|
||||
[1, 1],
|
||||
[0, -1],
|
||||
[0, 1],
|
||||
[-1, -1],
|
||||
[-1, 0],
|
||||
[-1, 1],
|
||||
];
|
||||
|
||||
const rendered = useRef<boolean>(false);
|
||||
const currentGraphicRef = useRef<Graphic | null>(null);
|
||||
currentGraphicRef.current = currentTarget;
|
||||
const cellcodeQueries = operations.map(
|
||||
(operation) => `cellcode = '1kmN${northNumber + operation[0]}E${eastNumber + operation[1]}'`
|
||||
);
|
||||
return `cellcode = '${cellcode}' OR ` + cellcodeQueries.join(' OR ');
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// get map image layer from sublayer
|
||||
const getMapImageLayer = (layerURL: string): MapImageLayer | undefined => {
|
||||
if (view) {
|
||||
const filteredLayerViews = view.allLayerViews.filter((layerView) => {
|
||||
const regex = /^https:\/\/.+\/MapServer/g;
|
||||
const matches = layerURL.match(regex);
|
||||
let mapImageLayerURL;
|
||||
if (matches && matches.length > 0) mapImageLayerURL = matches[0];
|
||||
|
||||
if (layerView.layer.type === 'map-image') {
|
||||
return (layerView.layer as MapImageLayer).url === mapImageLayerURL;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
let mapImageLayer;
|
||||
if (filteredLayerViews.length > 0) {
|
||||
mapImageLayer = filteredLayerViews.at(0).layer as MapImageLayer;
|
||||
}
|
||||
|
||||
return mapImageLayer;
|
||||
}
|
||||
};
|
||||
|
||||
// handle toggle layer visibility
|
||||
const handleToggleVisibility = (event: any) => {
|
||||
const layerItem = event.target;
|
||||
|
||||
const layerId = layerItem.getAttribute('text');
|
||||
const icon = layerItem.getAttribute('icon');
|
||||
|
||||
if (layerId) {
|
||||
const layer = view.map?.findLayerById(layerId);
|
||||
if (icon === 'view-hide') {
|
||||
layerItem.setAttribute('icon', 'view-visible');
|
||||
if (layer) {
|
||||
layer.visible = true;
|
||||
}
|
||||
} else {
|
||||
layerItem.setAttribute('icon', 'view-hide');
|
||||
if (layer) {
|
||||
layer.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// handle zoom to feature
|
||||
const handleZoomTo = async (event: any) => {
|
||||
const layerURL = event.target.getAttribute('text');
|
||||
const res = await fetch(layerURL + '?f=json');
|
||||
const json = await res.json();
|
||||
|
||||
const mapImageLayer = getMapImageLayer(layerURL);
|
||||
const sr = mapImageLayer?.spatialReference;
|
||||
const geometry = json.feature?.geometry;
|
||||
|
||||
view.graphics.removeAll();
|
||||
const higlightOptions = view.highlightOptions;
|
||||
if (geometry.x && geometry.y && higlightOptions && higlightOptions.color && higlightOptions.fillOpacity) {
|
||||
const point = new Point({
|
||||
x: geometry.x,
|
||||
y: geometry.y,
|
||||
spatialReference: sr,
|
||||
});
|
||||
const graphic = new Graphic({
|
||||
geometry: point,
|
||||
symbol: {
|
||||
type: 'simple-marker',
|
||||
style: 'circle',
|
||||
size: '8px',
|
||||
color: [
|
||||
higlightOptions.color.r,
|
||||
higlightOptions.color.g,
|
||||
higlightOptions.color.b,
|
||||
higlightOptions.fillOpacity,
|
||||
],
|
||||
outline: {
|
||||
color: [higlightOptions.color.r, higlightOptions.color.g, higlightOptions.color.b, higlightOptions.color.a],
|
||||
width: 1,
|
||||
},
|
||||
} as unknown as SimpleMarkerSymbol,
|
||||
});
|
||||
view.graphics.add(graphic);
|
||||
view.goTo(geodesicBuffer(point, 1000, 'meters'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (geometry.paths && higlightOptions && higlightOptions.color && higlightOptions.fillOpacity) {
|
||||
const polyline = new Polyline({
|
||||
paths: geometry.paths,
|
||||
spatialReference: sr,
|
||||
});
|
||||
const graphic = new Graphic({
|
||||
geometry: polyline,
|
||||
symbol: {
|
||||
type: 'simple-line',
|
||||
color: [higlightOptions.color.r, higlightOptions.color.g, higlightOptions.color.b],
|
||||
width: '2px',
|
||||
} as unknown as SimpleLineSymbol,
|
||||
});
|
||||
view.graphics.add(graphic);
|
||||
view.goTo(geodesicBuffer(polyline.extent.center, 5000, 'meters'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (geometry.rings && higlightOptions && higlightOptions.color && higlightOptions.fillOpacity) {
|
||||
const polygon = new Polygon({
|
||||
rings: geometry.rings,
|
||||
spatialReference: sr,
|
||||
});
|
||||
const graphic = new Graphic({
|
||||
geometry: polygon,
|
||||
symbol: {
|
||||
type: 'simple-fill',
|
||||
color: [
|
||||
higlightOptions.color.r,
|
||||
higlightOptions.color.g,
|
||||
higlightOptions.color.b,
|
||||
higlightOptions.fillOpacity,
|
||||
],
|
||||
outline: {
|
||||
color: [higlightOptions.color.r, higlightOptions.color.g, higlightOptions.color.b, higlightOptions.color.a],
|
||||
width: 1,
|
||||
},
|
||||
} as unknown as SimpleFillSymbol,
|
||||
});
|
||||
view.graphics.add(graphic);
|
||||
view.goTo(polygon);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// remove layers from layer tree by given filter
|
||||
const removeLayers = (layers: (CustomGroupLayer | CustomLayer)[], keepLayer: any): any => {
|
||||
// remove layers from layer tree by given filter
|
||||
const removeLayers = (layers: (CustomGroupLayer | CustomLayer)[], keepLayer: any): any => {
|
||||
return layers
|
||||
.filter((layer) => keepLayer(layer))
|
||||
.map((layer) => {
|
||||
|
@ -225,10 +106,10 @@ export default function SearchComponent({ view }: { view: MapView }) {
|
|||
return layer;
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// get custom layer objects from layer tree
|
||||
const getLayerObjects = (layers: any) => {
|
||||
// get custom layer objects from layer tree
|
||||
const getLayerObjects = (layers: any) => {
|
||||
return layers.map((layer: any) => {
|
||||
if (layer.layers) {
|
||||
return {
|
||||
|
@ -242,10 +123,20 @@ export default function SearchComponent({ view }: { view: MapView }) {
|
|||
return { id: layer.id, type: layer.type, title: layer.title, visible: layer.visible };
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// build query string for custom search source (BEV geocoding service)
|
||||
const buildQueryString = (searchTerm: string) => `?term=${encodeURI(searchTerm)}`;
|
||||
// build query string for custom search source (BEV geocoding service)
|
||||
const buildQueryString = (searchTerm: string) => `?term=${encodeURI(searchTerm)}`;
|
||||
|
||||
// custom React component
|
||||
export default function SearchComponent({ view }: { view: MapView }) {
|
||||
const [currentTarget, setCurrentTarget] = useState<Graphic | null>(null);
|
||||
|
||||
const rendered = useRef<boolean>(false);
|
||||
const currentGraphicRef = useRef<Graphic | null>(null);
|
||||
currentGraphicRef.current = currentTarget;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const url = 'https://kataster.bev.gv.at/api/all4map';
|
||||
const customSearchSource = new SearchSource({
|
||||
|
@ -340,32 +231,171 @@ export default function SearchComponent({ view }: { view: MapView }) {
|
|||
},
|
||||
});
|
||||
|
||||
// create query from cellcode for 3x3 neighbourhood
|
||||
const createQueryFromCellcode = (cellcode: string) => {
|
||||
const { north, east }: any = cellcode.match(/N(?<north>\d+)E(?<east>\d+)/)?.groups;
|
||||
const northNumber = parseInt(north);
|
||||
const eastNumber = parseInt(east);
|
||||
const operations = [
|
||||
[1, -1],
|
||||
[1, 0],
|
||||
[1, 1],
|
||||
[0, -1],
|
||||
[0, 1],
|
||||
[-1, -1],
|
||||
[-1, 0],
|
||||
[-1, 1],
|
||||
];
|
||||
// get map image layer from sublayer
|
||||
const getMapImageLayer = (layerURL: string): MapImageLayer | undefined => {
|
||||
if (view) {
|
||||
const filteredLayerViews = view.allLayerViews.filter((layerView) => {
|
||||
const regex = /^https:\/\/.+\/MapServer/g;
|
||||
const matches = layerURL.match(regex);
|
||||
let mapImageLayerURL;
|
||||
if (matches && matches.length > 0) mapImageLayerURL = matches[0];
|
||||
|
||||
const cellcodeQueries = operations.map(
|
||||
(operation) => `cellcode = '10kmN${northNumber + operation[0]}E${eastNumber + operation[1]}'`
|
||||
);
|
||||
return `cellcode = '${cellcode}' OR ` + cellcodeQueries.join(' OR ');
|
||||
if (layerView.layer.type === 'map-image') {
|
||||
return (layerView.layer as MapImageLayer).url === mapImageLayerURL;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
let mapImageLayer;
|
||||
if (filteredLayerViews.length > 0) {
|
||||
mapImageLayer = filteredLayerViews.at(0).layer as MapImageLayer;
|
||||
}
|
||||
|
||||
return mapImageLayer;
|
||||
}
|
||||
};
|
||||
|
||||
// handle toggle layer visibility
|
||||
const handleToggleVisibility = (event: any) => {
|
||||
const layerItem = event.target;
|
||||
|
||||
const layerId = layerItem.getAttribute('text');
|
||||
const icon = layerItem.getAttribute('icon');
|
||||
|
||||
if (layerId) {
|
||||
const layer = view.map?.findLayerById(layerId);
|
||||
if (icon === 'view-hide') {
|
||||
layerItem.setAttribute('icon', 'view-visible');
|
||||
if (layer) {
|
||||
layer.visible = true;
|
||||
}
|
||||
} else {
|
||||
layerItem.setAttribute('icon', 'view-hide');
|
||||
if (layer) {
|
||||
layer.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (rendered.current) return;
|
||||
rendered.current = true;
|
||||
// handle zoom to feature
|
||||
const handleZoomTo = async (event: any) => {
|
||||
const layerURL = event.target.getAttribute('text');
|
||||
const res = await fetch(layerURL + '?f=json');
|
||||
const json = await res.json();
|
||||
|
||||
const mapImageLayer = getMapImageLayer(layerURL);
|
||||
const sr = mapImageLayer?.spatialReference;
|
||||
const geometry = json.feature?.geometry;
|
||||
|
||||
view.graphics.removeAll();
|
||||
const higlightOptions = view.highlightOptions;
|
||||
if (geometry.x && geometry.y && higlightOptions && higlightOptions.color && higlightOptions.fillOpacity) {
|
||||
const point = new Point({
|
||||
x: geometry.x,
|
||||
y: geometry.y,
|
||||
spatialReference: sr,
|
||||
});
|
||||
const graphic = new Graphic({
|
||||
geometry: point,
|
||||
symbol: {
|
||||
type: 'simple-marker',
|
||||
style: 'circle',
|
||||
size: '8px',
|
||||
color: [
|
||||
higlightOptions.color.r,
|
||||
higlightOptions.color.g,
|
||||
higlightOptions.color.b,
|
||||
higlightOptions.fillOpacity,
|
||||
],
|
||||
outline: {
|
||||
color: [
|
||||
higlightOptions.color.r,
|
||||
higlightOptions.color.g,
|
||||
higlightOptions.color.b,
|
||||
higlightOptions.color.a,
|
||||
],
|
||||
width: 1,
|
||||
},
|
||||
} as unknown as SimpleMarkerSymbol,
|
||||
});
|
||||
view.graphics.add(graphic);
|
||||
view.goTo(geodesicBuffer(point, 1000, 'meters'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (geometry.paths && higlightOptions && higlightOptions.color && higlightOptions.fillOpacity) {
|
||||
const polyline = new Polyline({
|
||||
paths: geometry.paths,
|
||||
spatialReference: sr,
|
||||
});
|
||||
const graphic = new Graphic({
|
||||
geometry: polyline,
|
||||
symbol: {
|
||||
type: 'simple-line',
|
||||
color: [higlightOptions.color.r, higlightOptions.color.g, higlightOptions.color.b],
|
||||
width: '2px',
|
||||
} as unknown as SimpleLineSymbol,
|
||||
});
|
||||
view.graphics.add(graphic);
|
||||
view.goTo(geodesicBuffer(polyline.extent.center, 5000, 'meters'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (geometry.rings && higlightOptions && higlightOptions.color && higlightOptions.fillOpacity) {
|
||||
const polygon = new Polygon({
|
||||
rings: geometry.rings,
|
||||
spatialReference: sr,
|
||||
});
|
||||
const graphic = new Graphic({
|
||||
geometry: polygon,
|
||||
symbol: {
|
||||
type: 'simple-fill',
|
||||
color: [
|
||||
higlightOptions.color.r,
|
||||
higlightOptions.color.g,
|
||||
higlightOptions.color.b,
|
||||
higlightOptions.fillOpacity,
|
||||
],
|
||||
outline: {
|
||||
color: [
|
||||
higlightOptions.color.r,
|
||||
higlightOptions.color.g,
|
||||
higlightOptions.color.b,
|
||||
higlightOptions.color.a,
|
||||
],
|
||||
width: 1,
|
||||
},
|
||||
} as unknown as SimpleFillSymbol,
|
||||
});
|
||||
view.graphics.add(graphic);
|
||||
view.goTo(polygon);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// watch for visibility changes
|
||||
const handle = watch(
|
||||
() => view.map.allLayers.map((layer) => [layer.id, layer.visible]),
|
||||
(newValues, oldValues) => {
|
||||
newValues.forEach((value, key) => {
|
||||
// visibility changed
|
||||
if (oldValues.at(key) && value[1] !== oldValues.at(key)[1]) {
|
||||
const layerId = value[0] as string;
|
||||
const calciteAction = document.querySelector(`calcite-action[text='${layerId}']`);
|
||||
if (calciteAction) {
|
||||
if (value[1]) {
|
||||
calciteAction.setAttribute('icon', 'view-visible');
|
||||
} else {
|
||||
calciteAction.setAttribute('icon', 'view-hide');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
// add data index layer
|
||||
view.map.layers.push(indexLayer);
|
||||
|
||||
|
@ -377,11 +407,13 @@ export default function SearchComponent({ view }: { view: MapView }) {
|
|||
includeDefaultSources: false,
|
||||
});
|
||||
|
||||
// empty top-left corner of MapView for Search component
|
||||
view.ui.add(search, 'top-left');
|
||||
|
||||
// add event handler for select-result events
|
||||
search.on('select-result', (event) => {
|
||||
view.closePopup();
|
||||
view.graphics.removeAll();
|
||||
|
||||
// get selected feature and display it on map
|
||||
const graphic = event.result.feature;
|
||||
|
@ -396,9 +428,6 @@ export default function SearchComponent({ view }: { view: MapView }) {
|
|||
setCurrentTarget(graphic);
|
||||
}
|
||||
|
||||
view.graphics.removeAll();
|
||||
view.graphics.add(graphic);
|
||||
|
||||
// query for intersecting features
|
||||
indexLayer
|
||||
.queryFeatures({
|
||||
|
@ -496,19 +525,69 @@ export default function SearchComponent({ view }: { view: MapView }) {
|
|||
}
|
||||
|
||||
const text = sublayer?.id ? sublayer?.id.toString() : '';
|
||||
|
||||
let sublayerWasVisible: boolean = false;
|
||||
let mapImageLayerWasVisible: boolean = false;
|
||||
const handleMouseEnter: MouseEventHandler<HTMLCalciteAccordionItemElement> = (event) => {
|
||||
(event.target as HTMLElement).classList.add('bg-gray-100');
|
||||
if (mapImageLayer && !mapImageLayer.visible) {
|
||||
mapImageLayerWasVisible = false;
|
||||
mapImageLayer.visible = true;
|
||||
} else {
|
||||
mapImageLayerWasVisible = true;
|
||||
}
|
||||
|
||||
if (sublayer && !sublayer.visible) {
|
||||
sublayerWasVisible = false;
|
||||
sublayer.visible = true;
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave: MouseEventHandler<HTMLCalciteAccordionItemElement> = (event) => {
|
||||
(event.target as HTMLElement).classList.remove('bg-gray-100');
|
||||
if (mapImageLayer && !mapImageLayerWasVisible) {
|
||||
mapImageLayer.visible = false;
|
||||
}
|
||||
|
||||
if (sublayer && !sublayerWasVisible) {
|
||||
sublayer.visible = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSublayerVisibilityToggle: MouseEventHandler<HTMLCalciteActionElement> = (event: any) => {
|
||||
if (mapImageLayer && sublayer) {
|
||||
if (event.target?.getAttribute('icon') === 'view-hide') {
|
||||
mapImageLayerWasVisible = true;
|
||||
mapImageLayer.visible = true;
|
||||
}
|
||||
|
||||
if (event.target?.getAttribute('icon') === 'view-visible') {
|
||||
event.target?.setAttribute('icon', 'view-hide');
|
||||
sublayer.visible = false;
|
||||
sublayerWasVisible = false;
|
||||
} else {
|
||||
event.target?.setAttribute('icon', 'view-visible');
|
||||
sublayer.visible = true;
|
||||
sublayerWasVisible = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// create UI item for sublayer
|
||||
const accordionItem = (
|
||||
<CalciteAccordionItem heading={sublayer?.title} key={sublayer?.id}>
|
||||
<CalciteAccordionItem
|
||||
heading={sublayer?.title}
|
||||
key={sublayer?.id}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<CalciteAction
|
||||
slot="actions-end"
|
||||
icon={sublayer?.visible ? 'view-visible' : 'view-hide'}
|
||||
text={text}
|
||||
appearance="transparent"
|
||||
onClick={() => {
|
||||
if (sublayer) {
|
||||
sublayer.visible = !sublayer.visible;
|
||||
}
|
||||
}}
|
||||
scale="s"
|
||||
onClick={handleSublayerVisibilityToggle}
|
||||
></CalciteAction>
|
||||
|
||||
<CalciteList>
|
||||
|
@ -565,7 +644,7 @@ export default function SearchComponent({ view }: { view: MapView }) {
|
|||
(layer.type === 'group' &&
|
||||
((layer as CustomGroupLayer).layers.length === 0 ||
|
||||
(layer as CustomGroupLayer).layers.every(
|
||||
(child) => child.type === 'imagery' || child.type === 'imagery-tile'
|
||||
(child) => child.type === 'imagery' || child.type === 'imagery-tile' || child.type === 'tile'
|
||||
))) ||
|
||||
layer.type === 'feature' ||
|
||||
layer.type === 'imagery' ||
|
||||
|
@ -666,7 +745,7 @@ export default function SearchComponent({ view }: { view: MapView }) {
|
|||
};
|
||||
|
||||
indexLayer.popupTemplate = popupTemplate as unknown as PopupTemplate;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
// output: 'standalone',
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
|
713
package-lock.json
generated
713
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
@ -3,11 +3,12 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_OPTIONS='--inspect' next dev -p 5000",
|
||||
"build": "npm run copy && next build",
|
||||
"start": "npm run copy && next start -p 5000",
|
||||
"dev": "next dev",
|
||||
"build": "npm run copy-arcgis-maps-assets && npm run copy-calcite-components-assets && next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"copy": "ncp ./node_modules/@arcgis/core/assets ./public/assets"
|
||||
"copy-arcgis-maps-assets": "ncp ./node_modules/@arcgis/core/assets ./public/assets",
|
||||
"copy-calcite-components-assets": "ncp ./node_modules/@esri/calcite-components/dist/calcite/assets ./public/assets/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@arcgis/core": "^4.27.6",
|
||||
|
@ -19,17 +20,18 @@
|
|||
"eslint": "8.45.0",
|
||||
"eslint-config-next": "13.4.12",
|
||||
"i18next": "^23.4.4",
|
||||
"ncp": "^2.0.0",
|
||||
"negotiator": "^0.6.3",
|
||||
"next": "^13.5.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-i18next": "^13.2.2",
|
||||
"sharp": "^0.32.6",
|
||||
"typescript": "5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cross-env": "^7.0.3",
|
||||
"ncp": "^2.0.0",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.3.3"
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 912 KiB |
Loading…
Reference in New Issue
Block a user