From c2660891ec427260118c0dd0519ee4fc19fc4c6d Mon Sep 17 00:00:00 2001 From: Max Burnette Date: Tue, 30 Jul 2024 09:09:18 -0500 Subject: [PATCH 1/7] Updates to Geospatial config --- docker-compose.previewers.yml | 14 ++++++++++ .../visualizations/Geospatial/Geospatial.tsx | 26 ++++++++++++------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/docker-compose.previewers.yml b/docker-compose.previewers.yml index 9b2e0ee01..f77363896 100644 --- a/docker-compose.previewers.yml +++ b/docker-compose.previewers.yml @@ -35,6 +35,20 @@ services: - geoserver_data:/opt/geoserver_data - geoserver_exts:/opt/additional_libs + geoshp-preview: + image: maxzilla2/geoshp + environment: + GEOSERVER_URL: http://geoserver:8080/geoserver/ + EXTERNAL_GEOSERVER_URL: http://localhost:8085/geoserver/ + GEOSERVER_USERNAME: admin + GEOSERVER_PASSWORD: geoserver + CLOWDER_VERSION: 2 + CLOWDER_URL: http://host.docker.internal:8000/ + RABBITMQ_URI: amqp://guest:guest@host.docker.internal:5672/%2F + networks: + - clowder2 + restart: unless-stopped + ## This image must be built from: ## https://github.com/clowder-framework/extractors-geo/blob/master/preview.geotiff/Dockerfile ## docker build -f Dockerfile -t clowder/extractors-geotiff-preview . diff --git a/frontend/src/components/visualizations/Geospatial/Geospatial.tsx b/frontend/src/components/visualizations/Geospatial/Geospatial.tsx index adb7ca886..2d4c18cda 100644 --- a/frontend/src/components/visualizations/Geospatial/Geospatial.tsx +++ b/frontend/src/components/visualizations/Geospatial/Geospatial.tsx @@ -2,11 +2,14 @@ import React, { useEffect, useRef, useState } from "react"; import Map from "ol/Map"; import View from "ol/View"; -import { OSM } from "ol/source"; -import TileLayer from "ol/layer/Tile"; -import Static from "ol/source/ImageStatic"; -import ImageLayer from "ol/layer/Image"; import { VisualizationConfigOut } from "../../../openapi/v2"; +import VectorLayer from "ol/layer/Vector"; +import VectorSource from "ol/source/Vector"; +import GeoJSON from "ol/format/GeoJSON"; +import TileLayer from "ol/layer/Tile"; +import { OSM } from "ol/source"; +import { bbox as bboxStrategy } from "ol/loadingstrategy"; +import { transformExtent } from "ol/proj"; type GeospatialProps = { visConfigEntry?: VisualizationConfigOut; @@ -42,20 +45,23 @@ export default function Geospatial(props: GeospatialProps) { bbox = vals.map((v) => parseFloat(v)); } }); + bbox = transformExtent(bbox, "EPSG:4326", "EPSG:3857"); const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]; + const source = new VectorSource({ + url: layerWMS, + format: new GeoJSON(), + strategy: bboxStrategy, + }); + const wms_map = new Map({ target: mapElement.current, layers: [ new TileLayer({ source: new OSM(), }), - new ImageLayer({ - source: new Static({ - url: layerWMS, - projection: "EPSG:3857", - imageExtent: bbox, - }), + new VectorLayer({ + source: source, }), ], view: new View({ From 70e480c14df644f35d5a892da25461fb0ed67bb8 Mon Sep 17 00:00:00 2001 From: Max Burnette Date: Tue, 30 Jul 2024 10:14:51 -0500 Subject: [PATCH 2/7] add floating label (janky) --- .../visualizations/Geospatial/Geospatial.tsx | 90 ++++++++++++++++--- 1 file changed, 79 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/visualizations/Geospatial/Geospatial.tsx b/frontend/src/components/visualizations/Geospatial/Geospatial.tsx index 2d4c18cda..ae0e81323 100644 --- a/frontend/src/components/visualizations/Geospatial/Geospatial.tsx +++ b/frontend/src/components/visualizations/Geospatial/Geospatial.tsx @@ -10,6 +10,7 @@ import TileLayer from "ol/layer/Tile"; import { OSM } from "ol/source"; import { bbox as bboxStrategy } from "ol/loadingstrategy"; import { transformExtent } from "ol/proj"; +import { FeatureLike } from "ol/Feature"; type GeospatialProps = { visConfigEntry?: VisualizationConfigOut; @@ -30,6 +31,17 @@ export default function Geospatial(props: GeospatialProps) { ) { const wms_url = String(visConfigEntry.parameters["WMS Layer URL"]); setLayerWMS(wms_url); + const attribute_url = wms_url.replace( + "GetFeature", + "describeFeatureType" + ); + fetch(attribute_url).then((response) => { + if (response.status === 200) { + response.json().then((json) => { + console.log(json); + }); + } + }); } } }, [visConfigEntry]); @@ -54,15 +66,17 @@ export default function Geospatial(props: GeospatialProps) { strategy: bboxStrategy, }); + const vecLayer = new VectorLayer({ + source: source, + }); + const wms_map = new Map({ target: mapElement.current, layers: [ new TileLayer({ source: new OSM(), }), - new VectorLayer({ - source: source, - }), + vecLayer, ], view: new View({ projection: "EPSG:3857", @@ -71,20 +85,74 @@ export default function Geospatial(props: GeospatialProps) { controls: [], }); wms_map.getView().fit(bbox); + + const info = document.getElementById("info"); + + let currentFeature: FeatureLike | undefined; + const displayFeatureInfo = function (pixel, target) { + const feature = target.closest(".ol-control") + ? undefined + : wms_map.forEachFeatureAtPixel(pixel, function (feature) { + return feature; + }); + if (feature && info) { + info.style.left = `${pixel[0]}px`; + info.style.top = `${pixel[1]}px`; + if (feature !== currentFeature) { + info.style.visibility = "visible"; + info.innerText = feature.get("STATE_NAME"); + } + } else { + if (info) info.style.visibility = "hidden"; + } + currentFeature = feature; + }; + + // Interactive behavior + wms_map.on("pointermove", function (evt) { + if (evt.dragging && info) { + info.style.visibility = "hidden"; + currentFeature = undefined; + return; + } + const pixel = wms_map.getEventPixel(evt.originalEvent); + displayFeatureInfo(pixel, evt.originalEvent.target); + }); + + wms_map.on("click", function (evt) { + displayFeatureInfo(evt.pixel, evt.originalEvent.target); + }); + wms_map.getTargetElement().addEventListener("pointerleave", function () { + currentFeature = undefined; + if (info) info.style.visibility = "hidden"; + }); + setMap(wms_map); } }, [layerWMS]); return (() => { return ( -
+ <> +
+
+
+ ); })(); } From 7ab061b7c00364700d6e9fb82f9036054ac02791 Mon Sep 17 00:00:00 2001 From: Max Burnette Date: Tue, 30 Jul 2024 11:54:26 -0500 Subject: [PATCH 3/7] update previewer with dropdowns --- .../visualizations/Geospatial/Geospatial.tsx | 117 +++++++++++++++++- 1 file changed, 115 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/visualizations/Geospatial/Geospatial.tsx b/frontend/src/components/visualizations/Geospatial/Geospatial.tsx index ae0e81323..b8333b12c 100644 --- a/frontend/src/components/visualizations/Geospatial/Geospatial.tsx +++ b/frontend/src/components/visualizations/Geospatial/Geospatial.tsx @@ -20,9 +20,32 @@ export default function Geospatial(props: GeospatialProps) { const { visConfigEntry } = props; const [layerWMS, setLayerWMS] = useState(undefined); + const [layerAttributes, setLayerAttributes] = useState( + undefined + ); + const [filterAttribute, setFilterAttribute] = useState( + undefined + ); + const [attributeValues, setAttributeValues] = useState( + undefined + ); + const [attributeValue, setAttributeValue] = useState( + undefined + ); + const [vectorRef, setVectorRef] = useState | undefined>( + undefined + ); const [map, setMap] = useState(undefined); const mapElement = useRef(); + function updateFilterAttribute(event) { + setFilterAttribute(event.target.value); + } + + function setAttributeValueFn(event) { + setAttributeValue(event.target.value); + } + useEffect(() => { if (visConfigEntry !== undefined) { if ( @@ -38,7 +61,11 @@ export default function Geospatial(props: GeospatialProps) { fetch(attribute_url).then((response) => { if (response.status === 200) { response.json().then((json) => { - console.log(json); + const attrs: string[] = []; + json["featureTypes"][0]["properties"].forEach((a) => { + attrs.push(a["name"]); + }); + setLayerAttributes(attrs); }); } }); @@ -60,8 +87,17 @@ export default function Geospatial(props: GeospatialProps) { bbox = transformExtent(bbox, "EPSG:4326", "EPSG:3857"); const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]; + let wms_url = layerWMS; + if (attributeValue != undefined) { + wms_url; + const params = new URLSearchParams(wms_url.split("?")[1]); + params.delete("bbox"); + wms_url = `${wms_url.split("?")[0]}?${params.toString()}`; + wms_url += `&CQL_FILTER=STATE_NAME='${attributeValue}'`; + } + const source = new VectorSource({ - url: layerWMS, + url: wms_url, format: new GeoJSON(), strategy: bboxStrategy, }); @@ -127,10 +163,65 @@ export default function Geospatial(props: GeospatialProps) { if (info) info.style.visibility = "hidden"; }); + setVectorRef(vecLayer); setMap(wms_map); } }, [layerWMS]); + useEffect(() => { + if (layerWMS !== undefined) { + // Determine bounding box extent & center point from URL + let bbox = [0, 0, 0, 0]; + const entries = layerWMS.split("&"); + entries.forEach((entry) => { + if (entry.startsWith("bbox=")) { + const vals = entry.replace("bbox=", "").split(","); + bbox = vals.map((v) => parseFloat(v)); + } + }); + bbox = transformExtent(bbox, "EPSG:4326", "EPSG:3857"); + const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]; + + let wms_url = layerWMS; + if (attributeValue != undefined) { + wms_url; + const params = new URLSearchParams(wms_url.split("?")[1]); + params.delete("bbox"); + wms_url = `${wms_url.split("?")[0]}?${params.toString()}`; + wms_url += `&CQL_FILTER=STATE_NAME='${attributeValue}'`; + } + + const source = new VectorSource({ + url: wms_url, + format: new GeoJSON(), + strategy: bboxStrategy, + }); + + const vecLayer = new VectorLayer({ + source: source, + }); + + if (map) { + if (vectorRef) map.removeLayer(vectorRef); + map.addLayer(vecLayer); + setVectorRef(vecLayer); + } + } + }, [layerWMS, attributeValue]); + + useEffect(() => { + const values: string[] = []; + if (vectorRef && filterAttribute) { + vectorRef + .getSource() + .getFeatures() + .forEach((feat: any) => { + values.push(feat["values_"][filterAttribute]); + }); + setAttributeValues(values); + } + }, [filterAttribute]); + return (() => { return ( <> @@ -152,6 +243,28 @@ export default function Geospatial(props: GeospatialProps) { }} />
+ {layerAttributes ? ( + <> + + + ) : ( + <> + )} + {attributeValues ? ( + <> + + + ) : ( + <> + )} ); })(); From efffe7597d957c272a51c8590ba12a55467c876d Mon Sep 17 00:00:00 2001 From: Max Burnette Date: Tue, 30 Jul 2024 13:14:30 -0500 Subject: [PATCH 4/7] split Geospatial vector and raster --- .../visualizations/Geospatial/Geospatial.tsx | 225 ++------------- .../visualizations/Geospatial/manifest.json | 4 - .../GeospatialVector/GeospatialVector.tsx | 271 ++++++++++++++++++ .../GeospatialVector/manifest.json | 25 ++ frontend/src/visualization.config.ts | 8 + 5 files changed, 323 insertions(+), 210 deletions(-) create mode 100644 frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx create mode 100644 frontend/src/components/visualizations/GeospatialVector/manifest.json diff --git a/frontend/src/components/visualizations/Geospatial/Geospatial.tsx b/frontend/src/components/visualizations/Geospatial/Geospatial.tsx index b8333b12c..adb7ca886 100644 --- a/frontend/src/components/visualizations/Geospatial/Geospatial.tsx +++ b/frontend/src/components/visualizations/Geospatial/Geospatial.tsx @@ -2,15 +2,11 @@ import React, { useEffect, useRef, useState } from "react"; import Map from "ol/Map"; import View from "ol/View"; -import { VisualizationConfigOut } from "../../../openapi/v2"; -import VectorLayer from "ol/layer/Vector"; -import VectorSource from "ol/source/Vector"; -import GeoJSON from "ol/format/GeoJSON"; -import TileLayer from "ol/layer/Tile"; import { OSM } from "ol/source"; -import { bbox as bboxStrategy } from "ol/loadingstrategy"; -import { transformExtent } from "ol/proj"; -import { FeatureLike } from "ol/Feature"; +import TileLayer from "ol/layer/Tile"; +import Static from "ol/source/ImageStatic"; +import ImageLayer from "ol/layer/Image"; +import { VisualizationConfigOut } from "../../../openapi/v2"; type GeospatialProps = { visConfigEntry?: VisualizationConfigOut; @@ -20,32 +16,9 @@ export default function Geospatial(props: GeospatialProps) { const { visConfigEntry } = props; const [layerWMS, setLayerWMS] = useState(undefined); - const [layerAttributes, setLayerAttributes] = useState( - undefined - ); - const [filterAttribute, setFilterAttribute] = useState( - undefined - ); - const [attributeValues, setAttributeValues] = useState( - undefined - ); - const [attributeValue, setAttributeValue] = useState( - undefined - ); - const [vectorRef, setVectorRef] = useState | undefined>( - undefined - ); const [map, setMap] = useState(undefined); const mapElement = useRef(); - function updateFilterAttribute(event) { - setFilterAttribute(event.target.value); - } - - function setAttributeValueFn(event) { - setAttributeValue(event.target.value); - } - useEffect(() => { if (visConfigEntry !== undefined) { if ( @@ -54,21 +27,6 @@ export default function Geospatial(props: GeospatialProps) { ) { const wms_url = String(visConfigEntry.parameters["WMS Layer URL"]); setLayerWMS(wms_url); - const attribute_url = wms_url.replace( - "GetFeature", - "describeFeatureType" - ); - fetch(attribute_url).then((response) => { - if (response.status === 200) { - response.json().then((json) => { - const attrs: string[] = []; - json["featureTypes"][0]["properties"].forEach((a) => { - attrs.push(a["name"]); - }); - setLayerAttributes(attrs); - }); - } - }); } } }, [visConfigEntry]); @@ -84,35 +42,21 @@ export default function Geospatial(props: GeospatialProps) { bbox = vals.map((v) => parseFloat(v)); } }); - bbox = transformExtent(bbox, "EPSG:4326", "EPSG:3857"); const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]; - let wms_url = layerWMS; - if (attributeValue != undefined) { - wms_url; - const params = new URLSearchParams(wms_url.split("?")[1]); - params.delete("bbox"); - wms_url = `${wms_url.split("?")[0]}?${params.toString()}`; - wms_url += `&CQL_FILTER=STATE_NAME='${attributeValue}'`; - } - - const source = new VectorSource({ - url: wms_url, - format: new GeoJSON(), - strategy: bboxStrategy, - }); - - const vecLayer = new VectorLayer({ - source: source, - }); - const wms_map = new Map({ target: mapElement.current, layers: [ new TileLayer({ source: new OSM(), }), - vecLayer, + new ImageLayer({ + source: new Static({ + url: layerWMS, + projection: "EPSG:3857", + imageExtent: bbox, + }), + }), ], view: new View({ projection: "EPSG:3857", @@ -121,151 +65,20 @@ export default function Geospatial(props: GeospatialProps) { controls: [], }); wms_map.getView().fit(bbox); - - const info = document.getElementById("info"); - - let currentFeature: FeatureLike | undefined; - const displayFeatureInfo = function (pixel, target) { - const feature = target.closest(".ol-control") - ? undefined - : wms_map.forEachFeatureAtPixel(pixel, function (feature) { - return feature; - }); - if (feature && info) { - info.style.left = `${pixel[0]}px`; - info.style.top = `${pixel[1]}px`; - if (feature !== currentFeature) { - info.style.visibility = "visible"; - info.innerText = feature.get("STATE_NAME"); - } - } else { - if (info) info.style.visibility = "hidden"; - } - currentFeature = feature; - }; - - // Interactive behavior - wms_map.on("pointermove", function (evt) { - if (evt.dragging && info) { - info.style.visibility = "hidden"; - currentFeature = undefined; - return; - } - const pixel = wms_map.getEventPixel(evt.originalEvent); - displayFeatureInfo(pixel, evt.originalEvent.target); - }); - - wms_map.on("click", function (evt) { - displayFeatureInfo(evt.pixel, evt.originalEvent.target); - }); - wms_map.getTargetElement().addEventListener("pointerleave", function () { - currentFeature = undefined; - if (info) info.style.visibility = "hidden"; - }); - - setVectorRef(vecLayer); setMap(wms_map); } }, [layerWMS]); - useEffect(() => { - if (layerWMS !== undefined) { - // Determine bounding box extent & center point from URL - let bbox = [0, 0, 0, 0]; - const entries = layerWMS.split("&"); - entries.forEach((entry) => { - if (entry.startsWith("bbox=")) { - const vals = entry.replace("bbox=", "").split(","); - bbox = vals.map((v) => parseFloat(v)); - } - }); - bbox = transformExtent(bbox, "EPSG:4326", "EPSG:3857"); - const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]; - - let wms_url = layerWMS; - if (attributeValue != undefined) { - wms_url; - const params = new URLSearchParams(wms_url.split("?")[1]); - params.delete("bbox"); - wms_url = `${wms_url.split("?")[0]}?${params.toString()}`; - wms_url += `&CQL_FILTER=STATE_NAME='${attributeValue}'`; - } - - const source = new VectorSource({ - url: wms_url, - format: new GeoJSON(), - strategy: bboxStrategy, - }); - - const vecLayer = new VectorLayer({ - source: source, - }); - - if (map) { - if (vectorRef) map.removeLayer(vectorRef); - map.addLayer(vecLayer); - setVectorRef(vecLayer); - } - } - }, [layerWMS, attributeValue]); - - useEffect(() => { - const values: string[] = []; - if (vectorRef && filterAttribute) { - vectorRef - .getSource() - .getFeatures() - .forEach((feat: any) => { - values.push(feat["values_"][filterAttribute]); - }); - setAttributeValues(values); - } - }, [filterAttribute]); - return (() => { return ( - <> -
-
-
- {layerAttributes ? ( - <> - - - ) : ( - <> - )} - {attributeValues ? ( - <> - - - ) : ( - <> - )} - +
); })(); } diff --git a/frontend/src/components/visualizations/Geospatial/manifest.json b/frontend/src/components/visualizations/Geospatial/manifest.json index f8a0e8114..32669230c 100644 --- a/frontend/src/components/visualizations/Geospatial/manifest.json +++ b/frontend/src/components/visualizations/Geospatial/manifest.json @@ -13,10 +13,6 @@ "name": "Geospatial", "mainType": "image", "mimeTypes": [ - "application/zip", - "application/x-zip", - "application/x-7z-compressed", - "multi/files-zipped", "image/tif", "image/tiff" ], diff --git a/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx b/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx new file mode 100644 index 000000000..cf7191a47 --- /dev/null +++ b/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx @@ -0,0 +1,271 @@ +import React, { useEffect, useRef, useState } from "react"; + +import Map from "ol/Map"; +import View from "ol/View"; +import { VisualizationConfigOut } from "../../../openapi/v2"; +import VectorLayer from "ol/layer/Vector"; +import VectorSource from "ol/source/Vector"; +import GeoJSON from "ol/format/GeoJSON"; +import TileLayer from "ol/layer/Tile"; +import { OSM } from "ol/source"; +import { bbox as bboxStrategy } from "ol/loadingstrategy"; +import { transformExtent } from "ol/proj"; +import { FeatureLike } from "ol/Feature"; + +type GeospatialProps = { + visConfigEntry?: VisualizationConfigOut; +}; + +export default function GeospatialVector(props: GeospatialProps) { + const { visConfigEntry } = props; + + const [layerWMS, setLayerWMS] = useState(undefined); + const [layerAttributes, setLayerAttributes] = useState( + undefined + ); + const [filterAttribute, setFilterAttribute] = useState( + undefined + ); + const [attributeValues, setAttributeValues] = useState( + undefined + ); + const [attributeValue, setAttributeValue] = useState( + undefined + ); + const [vectorRef, setVectorRef] = useState | undefined>( + undefined + ); + const [map, setMap] = useState(undefined); + const mapElement = useRef(); + + function updateFilterAttribute(event) { + setFilterAttribute(event.target.value); + } + + function setAttributeValueFn(event) { + setAttributeValue(event.target.value); + } + + useEffect(() => { + if (visConfigEntry !== undefined) { + if ( + visConfigEntry.parameters && + visConfigEntry.parameters["WMS Layer URL"] + ) { + const wms_url = String(visConfigEntry.parameters["WMS Layer URL"]); + setLayerWMS(wms_url); + const attribute_url = wms_url.replace( + "GetFeature", + "describeFeatureType" + ); + fetch(attribute_url).then((response) => { + if (response.status === 200) { + response.json().then((json) => { + const attrs: string[] = []; + json["featureTypes"][0]["properties"].forEach((a) => { + attrs.push(a["name"]); + }); + setLayerAttributes(attrs); + }); + } + }); + } + } + }, [visConfigEntry]); + + useEffect(() => { + if (layerWMS !== undefined) { + // Determine bounding box extent & center point from URL + let bbox = [0, 0, 0, 0]; + const entries = layerWMS.split("&"); + entries.forEach((entry) => { + if (entry.startsWith("bbox=")) { + const vals = entry.replace("bbox=", "").split(","); + bbox = vals.map((v) => parseFloat(v)); + } + }); + bbox = transformExtent(bbox, "EPSG:4326", "EPSG:3857"); + const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]; + + let wms_url = layerWMS; + if (attributeValue != undefined) { + wms_url; + const params = new URLSearchParams(wms_url.split("?")[1]); + params.delete("bbox"); + wms_url = `${wms_url.split("?")[0]}?${params.toString()}`; + wms_url += `&CQL_FILTER=STATE_NAME='${attributeValue}'`; + } + + const source = new VectorSource({ + url: wms_url, + format: new GeoJSON(), + strategy: bboxStrategy, + }); + + const vecLayer = new VectorLayer({ + source: source, + }); + + const wms_map = new Map({ + target: mapElement.current, + layers: [ + new TileLayer({ + source: new OSM(), + }), + vecLayer, + ], + view: new View({ + projection: "EPSG:3857", + center: center, + }), + controls: [], + }); + wms_map.getView().fit(bbox); + + const info = document.getElementById("info"); + + let currentFeature: FeatureLike | undefined; + const displayFeatureInfo = function (pixel, target) { + const feature = target.closest(".ol-control") + ? undefined + : wms_map.forEachFeatureAtPixel(pixel, function (feature) { + return feature; + }); + if (feature && info) { + info.style.left = `${pixel[0]}px`; + info.style.top = `${pixel[1]}px`; + if (feature !== currentFeature) { + info.style.visibility = "visible"; + info.innerText = feature.get("STATE_NAME"); + } + } else { + if (info) info.style.visibility = "hidden"; + } + currentFeature = feature; + }; + + // Interactive behavior + wms_map.on("pointermove", function (evt) { + if (evt.dragging && info) { + info.style.visibility = "hidden"; + currentFeature = undefined; + return; + } + const pixel = wms_map.getEventPixel(evt.originalEvent); + displayFeatureInfo(pixel, evt.originalEvent.target); + }); + + wms_map.on("click", function (evt) { + displayFeatureInfo(evt.pixel, evt.originalEvent.target); + }); + wms_map.getTargetElement().addEventListener("pointerleave", function () { + currentFeature = undefined; + if (info) info.style.visibility = "hidden"; + }); + + setVectorRef(vecLayer); + setMap(wms_map); + } + }, [layerWMS]); + + useEffect(() => { + if (layerWMS !== undefined) { + // Determine bounding box extent & center point from URL + let bbox = [0, 0, 0, 0]; + const entries = layerWMS.split("&"); + entries.forEach((entry) => { + if (entry.startsWith("bbox=")) { + const vals = entry.replace("bbox=", "").split(","); + bbox = vals.map((v) => parseFloat(v)); + } + }); + bbox = transformExtent(bbox, "EPSG:4326", "EPSG:3857"); + const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]; + + let wms_url = layerWMS; + if (attributeValue != undefined) { + wms_url; + const params = new URLSearchParams(wms_url.split("?")[1]); + params.delete("bbox"); + wms_url = `${wms_url.split("?")[0]}?${params.toString()}`; + wms_url += `&CQL_FILTER=STATE_NAME='${attributeValue}'`; + } + + const source = new VectorSource({ + url: wms_url, + format: new GeoJSON(), + strategy: bboxStrategy, + }); + + const vecLayer = new VectorLayer({ + source: source, + }); + + if (map) { + if (vectorRef) map.removeLayer(vectorRef); + map.addLayer(vecLayer); + setVectorRef(vecLayer); + } + } + }, [layerWMS, attributeValue]); + + useEffect(() => { + const values: string[] = []; + if (vectorRef && filterAttribute) { + vectorRef + .getSource() + .getFeatures() + .forEach((feat: any) => { + values.push(feat["values_"][filterAttribute]); + }); + setAttributeValues(values); + } + }, [filterAttribute]); + + return (() => { + return ( + <> +
+
+
+ {layerAttributes ? ( + <> + + + ) : ( + <> + )} + {attributeValues ? ( + <> + + + ) : ( + <> + )} + + ); + })(); +} diff --git a/frontend/src/components/visualizations/GeospatialVector/manifest.json b/frontend/src/components/visualizations/GeospatialVector/manifest.json new file mode 100644 index 000000000..7b4c10475 --- /dev/null +++ b/frontend/src/components/visualizations/GeospatialVector/manifest.json @@ -0,0 +1,25 @@ +{ + "name": "geoserver-vector-viewer-component", + "version": "1.0.0", + "description": "A React component to render map services such as WFS layers found in metadata.", + "main": "GeospatialVector.tsx", + "dependencies": { + "clowder2-core": "1.0.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "ol": "^7.4.0" + }, + "visConfig": { + "name": "GeospatialVector", + "mainType": "application", + "mimeTypes": [ + "application/zip", + "application/x-zip", + "application/x-7z-compressed", + "multi/files-zipped" + ], + "props": { + "fileId": "string" + } + } +} diff --git a/frontend/src/visualization.config.ts b/frontend/src/visualization.config.ts index 35e8c627e..708c906b0 100644 --- a/frontend/src/visualization.config.ts +++ b/frontend/src/visualization.config.ts @@ -76,6 +76,14 @@ visComponentDefinitions.push({ component: React.createElement(registerComponent(configGeospatial)), }); +const configGeospatialVector = require("./components/visualizations/GeospatialVector/manifest.json"); +visComponentDefinitions.push({ + name: configGeospatialVector.name, + mainType: configGeospatialVector.visConfig.mainType, + mimeTypes: configGeospatialVector.visConfig.mimeTypes, + component: React.createElement(registerComponent(configGeospatialVector)), +}); + const configVega = require("./components/visualizations/CSV/manifest.json"); visComponentDefinitions.push({ name: configVega.name, From 2cd19d942e9cc4effaa3ad19bb7416dd602be176 Mon Sep 17 00:00:00 2001 From: Max Burnette Date: Tue, 30 Jul 2024 14:03:27 -0500 Subject: [PATCH 5/7] add clear filter --- .../GeospatialVector/GeospatialVector.tsx | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx b/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx index cf7191a47..5866d6373 100644 --- a/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx +++ b/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx @@ -20,6 +20,8 @@ export default function GeospatialVector(props: GeospatialProps) { const { visConfigEntry } = props; const [layerWMS, setLayerWMS] = useState(undefined); + const [layerDL, setLayerDL] = useState(undefined); + const [layerAttributes, setLayerAttributes] = useState( undefined ); @@ -46,6 +48,11 @@ export default function GeospatialVector(props: GeospatialProps) { setAttributeValue(event.target.value); } + function clearFilter() { + setFilterAttribute(undefined); + setAttributeValue("Show All"); + } + useEffect(() => { if (visConfigEntry !== undefined) { if ( @@ -54,6 +61,7 @@ export default function GeospatialVector(props: GeospatialProps) { ) { const wms_url = String(visConfigEntry.parameters["WMS Layer URL"]); setLayerWMS(wms_url); + const attribute_url = wms_url.replace( "GetFeature", "describeFeatureType" @@ -89,7 +97,6 @@ export default function GeospatialVector(props: GeospatialProps) { let wms_url = layerWMS; if (attributeValue != undefined) { - wms_url; const params = new URLSearchParams(wms_url.split("?")[1]); params.delete("bbox"); wms_url = `${wms_url.split("?")[0]}?${params.toString()}`; @@ -183,12 +190,15 @@ export default function GeospatialVector(props: GeospatialProps) { const center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]; let wms_url = layerWMS; - if (attributeValue != undefined) { - wms_url; + if ( + filterAttribute && + attributeValue != undefined && + attributeValue != "Show All" + ) { const params = new URLSearchParams(wms_url.split("?")[1]); params.delete("bbox"); wms_url = `${wms_url.split("?")[0]}?${params.toString()}`; - wms_url += `&CQL_FILTER=STATE_NAME='${attributeValue}'`; + wms_url += `&CQL_FILTER=${filterAttribute}='${attributeValue}'`; } const source = new VectorSource({ @@ -210,7 +220,20 @@ export default function GeospatialVector(props: GeospatialProps) { }, [layerWMS, attributeValue]); useEffect(() => { - const values: string[] = []; + if (layerWMS) { + const params = new URLSearchParams(layerWMS.split("?")[1]); + params.delete("bbox"); + params.delete("outputFormat"); + let dl_url = `${layerWMS.split("?")[0]}?${params.toString()}`; + if (attributeValue && filterAttribute) + dl_url += `&CQL_FILTER=${filterAttribute}='${attributeValue}'`; + dl_url += "&outputFormat=shape-zip"; + setLayerDL(dl_url); + } + }, [attributeValue]); + + useEffect(() => { + const values: string[] = ["Show All"]; if (vectorRef && filterAttribute) { vectorRef .getSource() @@ -245,6 +268,7 @@ export default function GeospatialVector(props: GeospatialProps) {
{layerAttributes ? ( <> + Field Name +
+ Value + + + + ) : ( + <> + )} + {layerDL ? ( + <> +
+ Download Data ) : ( <> From b7513b8e9f46474ef4d16c3eccdff5b998390241 Mon Sep 17 00:00:00 2001 From: Max Burnette Date: Tue, 30 Jul 2024 14:44:44 -0500 Subject: [PATCH 6/7] add table with attributes to the frontend --- .../GeospatialVector/GeospatialVector.tsx | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx b/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx index 5866d6373..c32b7e179 100644 --- a/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx +++ b/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx @@ -100,7 +100,7 @@ export default function GeospatialVector(props: GeospatialProps) { const params = new URLSearchParams(wms_url.split("?")[1]); params.delete("bbox"); wms_url = `${wms_url.split("?")[0]}?${params.toString()}`; - wms_url += `&CQL_FILTER=STATE_NAME='${attributeValue}'`; + wms_url += `&CQL_FILTER=${filterAttribute}='${attributeValue}'`; } const source = new VectorSource({ @@ -143,7 +143,15 @@ export default function GeospatialVector(props: GeospatialProps) { info.style.top = `${pixel[1]}px`; if (feature !== currentFeature) { info.style.visibility = "visible"; - info.innerText = feature.get("STATE_NAME"); + let label = + "" + ""; + const allProps = feature.getProperties(); + for (const key in allProps) { + if (key == "geometry") continue; + label += ``; + } + label += "
" + "FieldValue
${key}${allProps[key]}
"; + info.innerHTML = label; } } else { if (info) info.style.visibility = "hidden"; @@ -255,17 +263,7 @@ export default function GeospatialVector(props: GeospatialProps) { height: "300px", }} className="map-container" - > -
-
+ /> {layerAttributes ? ( <> Field Name @@ -300,6 +298,16 @@ export default function GeospatialVector(props: GeospatialProps) { ) : ( <> )} +
+
); })(); From 72f6e0554f379b62bb5a539c2b4ea19745e2e41f Mon Sep 17 00:00:00 2001 From: Max Burnette Date: Tue, 30 Jul 2024 14:56:14 -0500 Subject: [PATCH 7/7] filter and sort the list of attribute values --- .../GeospatialVector/GeospatialVector.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx b/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx index c32b7e179..60f9d3f81 100644 --- a/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx +++ b/frontend/src/components/visualizations/GeospatialVector/GeospatialVector.tsx @@ -147,8 +147,13 @@ export default function GeospatialVector(props: GeospatialProps) { "" + ""; const allProps = feature.getProperties(); for (const key in allProps) { - if (key == "geometry") continue; - label += ``; + if ( + !["operation_", "sp_region", "price", "prosperty_"].includes( + key + ) + ) + continue; + label += ``; } label += "
" + "FieldValue
${key}${allProps[key]}
${key}${allProps[key]}
"; info.innerHTML = label; @@ -247,9 +252,10 @@ export default function GeospatialVector(props: GeospatialProps) { .getSource() .getFeatures() .forEach((feat: any) => { - values.push(feat["values_"][filterAttribute]); + const val = feat["values_"][filterAttribute]; + if (!values.includes(val)) values.push(val); }); - setAttributeValues(values); + setAttributeValues(values.sort()); } }, [filterAttribute]);