Work on Popups

This commit is contained in:
Fuhrmann 2023-10-25 14:51:44 +02:00
parent a810f87461
commit 0d509d4e00
9 changed files with 1179 additions and 302 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules\\typescript\\lib"
}

View File

@ -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>;
}

View File

@ -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>

View File

@ -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';
}
}
};

View File

@ -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;
}

View File

@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
// output: 'standalone',
};
module.exports = nextConfig;

713
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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