From 430da26a9df0f62b88475ed3341f60eabacf1877 Mon Sep 17 00:00:00 2001 From: nkiro Date: Thu, 14 Nov 2024 11:35:57 +0300 Subject: [PATCH 1/9] Latest changes --- src/api/requests.ts | 358 ++++++++++++++++++++++++++++++++----- src/components/map/Map.tsx | 178 +++++++++--------- 2 files changed, 413 insertions(+), 123 deletions(-) diff --git a/src/api/requests.ts b/src/api/requests.ts index 3626187..2eda5a3 100644 --- a/src/api/requests.ts +++ b/src/api/requests.ts @@ -12,6 +12,7 @@ import WMTS from "ol/source/WMTS.js"; import WMTSTileGrid from "ol/tilegrid/WMTS.js"; import { get as getProjection } from "ol/proj.js"; import { getTopLeft, getWidth } from "ol/extent.js"; +import { el } from "date-fns/locale"; // let projection: any = null; @@ -32,9 +33,13 @@ for (let z = 0; z < 19; ++z) { matrixIds[z] = `EPSG:4326:${z}`; } -console.log('Raw GeoServer URL from env:', process.env.NEXT_PUBLIC_GEOSERVER_URL); -export const geoServerBaseUrl = process.env.NEXT_PUBLIC_GEOSERVER_URL?.trim().replace(/['"]/g, ''); -console.log('Processed GeoServer URL:', geoServerBaseUrl); +console.log( + "Raw GeoServer URL from env:", + process.env.NEXT_PUBLIC_GEOSERVER_URL +); +export const geoServerBaseUrl = + process.env.NEXT_PUBLIC_GEOSERVER_URL?.trim().replace(/['"]/g, ""); +console.log("Processed GeoServer URL:", geoServerBaseUrl); export const overlaysLayergroup_in_geoserver = process.env.NEXT_PUBLIC_OVERLAYS_LAYER_GROUP; @@ -317,7 +322,7 @@ export async function fetchLayerCapabilities() { // Fetch both capabilities in parallel const [wmsResponse, wmtsResponse] = await Promise.all([ fetchXML(wmsUrl), - fetchXML(wmtsUrl) + fetchXML(wmtsUrl), ]); if (!wmsResponse || !wmtsResponse) { @@ -327,15 +332,14 @@ export async function fetchLayerCapabilities() { // Parse capabilities const wmsParser = new WMSCapabilities(); const wmtsParser = new WMTSCapabilities(); - + const wmsResult = wmsParser.read(wmsResponse); const wmtsResult = wmtsParser.read(wmtsResponse); // Extract layer information const layers = extractLayerInfo(wmsResult, wmtsResult); - - return layers; + return layers; } catch (error) { console.error("Error fetching capabilities:", error); throw error; @@ -348,14 +352,14 @@ function extractLayerInfo(wmsData: any, wmtsData: any) { // Process WMS layers to get projection and extent info if (wmsData?.Capability?.Layer?.Layer) { wmsData.Capability.Layer.Layer.forEach((layer: any) => { - if (layer.Name?.startsWith('Dudu:')) { + if (layer.Name?.startsWith("Dudu:")) { layerInfo.set(layer.Name, { name: layer.Name, title: layer.Title, abstract: layer.Abstract, crs: layer.CRS || [], bbox: layer.BoundingBox || [], - defaultStyle: layer.Style?.[0]?.Name || '' + defaultStyle: layer.Style?.[0]?.Name || "", }); } }); @@ -364,13 +368,18 @@ function extractLayerInfo(wmsData: any, wmtsData: any) { // Add WMTS specific information if (wmtsData?.Contents?.Layer) { wmtsData.Contents.Layer.forEach((layer: any) => { - if (layer.Identifier?.startsWith('Dudu:') && layerInfo.has(layer.Identifier)) { + if ( + layer.Identifier?.startsWith("Dudu:") && + layerInfo.has(layer.Identifier) + ) { const info = layerInfo.get(layer.Identifier); layerInfo.set(layer.Identifier, { ...info, - tileMatrixSets: layer.TileMatrixSetLink?.map((link: any) => link.TileMatrixSet) || [], + tileMatrixSets: + layer.TileMatrixSetLink?.map((link: any) => link.TileMatrixSet) || + [], formats: layer.Format || [], - wmtsStyles: layer.Style?.map((style: any) => style.Identifier) || [] + wmtsStyles: layer.Style?.map((style: any) => style.Identifier) || [], }); } }); @@ -379,55 +388,318 @@ function extractLayerInfo(wmsData: any, wmtsData: any) { return Array.from(layerInfo.values()); } -// Add this new function while keeping all existing code -export async function fetchWMTSCapabilities() { +// Add new interface for layer group structure +interface GeoServerLayerGroup { + name: string; + title: string; + abstractTxt?: string; + workspace: string; + mode: string; + publishables: { + published: Array<{ + name: string; + href: string; + }>; + }; +} + +// Update fetchWMTSLayersInfo to handle CORS properly +async function fetchWMTSLayersInfo() { if (!geoServerBaseUrl) { throw new Error("GeoServer base URL is not defined"); } const capabilitiesUrl = `${geoServerBaseUrl}/geoserver/gwc/service/wmts?request=GetCapabilities`; - + try { - const response = await fetchXML(capabilitiesUrl); - if (!response) { - throw new Error("Failed to fetch WMTS capabilities"); + // Remove credentials and use simple fetch + const response = await fetch(capabilitiesUrl, { + method: "GET", + mode: "cors", // Explicitly set CORS mode + credentials: "omit", // Don't send credentials + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); } + const xmlText = await response.text(); const parser = new WMTSCapabilities(); - const result = parser.read(response); + const result = parser.read(xmlText); // Extract Dudu layers with their projections - const layers = result.Contents.Layer - .filter((layer: any) => layer.Identifier.startsWith('Dudu:')) - .map((layer: any) => ({ - name: layer.Identifier, - title: layer.Title, - matrixSet: layer.TileMatrixSetLink[0].TileMatrixSet, - supportedCRS: layer.TileMatrixSetLink[0].TileMatrixSet - })); - + const layers = result.Contents.Layer.filter((layer: any) => + layer.Identifier.startsWith("Dudu:") + ).map((layer: any) => ({ + name: layer.Identifier, + title: layer.Title, + matrixSet: layer.TileMatrixSetLink[0].TileMatrixSet, + supportedCRS: layer.TileMatrixSetLink[0].TileMatrixSet, + })); + + console.log("Successfully fetched WMTS layers:", layers); return layers; } catch (error) { console.error("Error fetching WMTS Capabilities:", error); - throw error; + return []; } } +// Add these interfaces at the top of the file +interface LayerGroupMember { + name: string; + title: string; + abstract?: string; +} + +interface LayerGroup { + name: string; + title: string; + layers: LayerGroupMember[]; + nestedGroups?: LayerGroup[]; +} + +// Add the extractLayerGroups function +function extractLayerGroups(capabilities: any): LayerGroup[] { + const groups: LayerGroup[] = []; + + function processLayer(layer: any) { + // Debug the layer structure + console.log("Processing layer:", { + name: layer.Name, + title: layer.Title, + hasChildLayers: !!layer.Layer, + childCount: layer.Layer?.length, + }); + + // Process this layer if it's a group + if (layer.Layer) { + // Check if this is a workspace layer group + if (layer.Name?.includes("Dudu:")) { + const group: LayerGroup = { + name: layer.Name, + title: layer.Title || layer.Name, + layers: [], + }; + + // Process all child layers + if (Array.isArray(layer.Layer)) { + layer.Layer.forEach((childLayer: any) => { + // Add individual layers to the group + if (childLayer.Name?.includes("Dudu:")) { + group.layers.push({ + name: childLayer.Name, + title: childLayer.Title || childLayer.Name, + abstract: childLayer.Abstract, + }); + } + }); + } + + // Only add groups that have layers + if (group.layers.length > 0) { + groups.push(group); + } + } + + // Recursively process child layers even if parent isn't in our workspace + if (Array.isArray(layer.Layer)) { + layer.Layer.forEach((childLayer: any) => { + processLayer(childLayer); + }); + } + } + } + + // Start processing from the root layer + if (capabilities?.Capability?.Layer) { + // Debug the root layer structure + console.log("Root layer structure:", { + name: capabilities.Capability.Layer.Name, + hasChildLayers: !!capabilities.Capability.Layer.Layer, + childCount: capabilities.Capability.Layer.Layer?.length, + }); + + processLayer(capabilities.Capability.Layer); + } + + return groups; +} + +// Update fetchLayerGroups to include more debugging +async function fetchLayerGroups(): Promise { + if (!geoServerBaseUrl) { + throw new Error("GeoServer base URL is not defined"); + } + + try { + const wmsUrl = + `${geoServerBaseUrl}/geoserver/wms?` + + "SERVICE=WMS&" + + "VERSION=1.3.0&" + + "REQUEST=GetCapabilities"; + + console.log("Fetching WMS capabilities from:", wmsUrl); + + const response = await fetch(wmsUrl, { + method: "GET", + mode: "cors", + credentials: "omit", + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const xmlText = await response.text(); + const parser = new WMSCapabilities(); + const result = parser.read(xmlText); + + // Debug the full capabilities structure + console.log("Full WMS Capabilities:", JSON.stringify(result, null, 2)); + + const layerGroups = extractLayerGroups(result); + console.log("Extracted layer groups:", layerGroups); + return layerGroups; + } catch (error) { + console.error("Error fetching WMS capabilities:", error); + return []; + } +} +function normalizeName(name: string): string { + return name.replace("Dudu:", "").trim(); +} + +// Update the type in fetchWMTSCapabilities where we use LayerGroup +export async function fetchWMTSCapabilities(): Promise { + try { + const [layerGroups, wmtsLayers] = await Promise.all([ + fetchLayerGroups(), + fetchWMTSLayersInfo(), + ]); + + console.log("Layer Groups:", layerGroups); + console.log("WMTS Layers:", wmtsLayers); + + if (!wmtsLayers.length) { + console.warn("No WMTS layers found"); + return []; + } + + // Create a map of normalized layer to group + const layerToGroupMap = new Map(); + + if (layerGroups && layerGroups.length > 0) { + layerGroups.forEach((group: LayerGroup) => { + if (group.layers && group.layers.length > 0) { + group.layers.forEach((layer: LayerGroupMember) => { + const normalizedLayerName = normalizeName(layer.name); + layerToGroupMap.set(normalizedLayerName, { + groupName: group.name, + groupTitle: group.title, + }); + }); + } + }); + } + + // Enrich WMTS layers with group information + const enrichedLayers = wmtsLayers.map((layer: WMTSLayer) => { + const normalizedLayerName = normalizeName( + layer.name.includes(":") ? layer.name.split(":")[1] : layer.name + ); + + console.log( + `Processing layer: ${layer.name} -> Normalized layer name: ${normalizedLayerName}` + ); + // const layerName = layer.name.includes(":") + // ? layer.name.split(":")[1] + // : layer.name + //check if the layer belongs to any group + const group = layerToGroupMap.get(normalizedLayerName); + console.log(`Group for ${layer.name}:`, group); + //if no group found , set it to "Ungrouped" + if (!group) { + return { + ...layer, + group: { + groupName: "Ungrouped", + groupTitle: "Ungrouped Layers", + }, + }; + } + //check if the group itself is a layer group + //skip addiing it to ungroupped if its part of a group + if (group.groupName && group.groupTitle) { + return { + ...layer, + group, + }; + } else { + //if no group found, set it to "Ungrouped" + //also if it is not a group itself + return { + ...layer, + group: { + groupName: "Ungrouped", + groupTitle: "Ungrouped Layers", + }, + }; + } + }); + // Filter out: + // 1. Layers that mistakenly ended up in "Ungrouped" when they belong to a group + // 2. Layer groups themselves that are showing in "Ungrouped" + const finalEnrichedLayers = enrichedLayers.filter((layer: WMTSLayer) => { + // 1. Remove layers that should belong to a group, not "Ungrouped" + const isLayerInGroup = layerToGroupMap.has(normalizeName(layer.name)); + + // 2. Remove layer groups from the ungrouped list (i.e., layers that are not "Ungrouped") + const isLayerGroup = !isLayerInGroup && layer.group?.groupName === "Ungrouped"; + + // Only include layers if they have a valid group or they are genuinely ungrouped layers + return !(isLayerGroup); + }); + + console.log("Final enriched layers:", enrichedLayers); + return enrichedLayers; + } catch (error) { + console.error("Error in fetchWMTSCapabilities:", error); + return []; + } +} + +// Update the interface for better type safety +interface WMTSLayer { + name: string; + title: string; + matrixSet: string; + supportedCRS: string; + group?: { + groupName: string; + groupTitle: string; + }; +} + export function getLegendUrl(layerName: string) { - if (!geoServerBaseUrl) return ''; - + if (!geoServerBaseUrl) return ""; + // Clean up the layer name - const cleanLayerName = layerName.includes(':') ? layerName : `Dudu:${layerName}`; - - return `${geoServerBaseUrl}/geoserver/wms?` + - 'REQUEST=GetLegendGraphic&' + - 'VERSION=1.0.0&' + - 'FORMAT=image/png&' + - 'WIDTH=20&' + - 'HEIGHT=20&' + - 'LEGEND_OPTIONS=forceLabels:on;fontAntiAliasing:true&' + // Added anti-aliasing + const cleanLayerName = layerName.includes(":") + ? layerName + : `Dudu:${layerName}`; + + return ( + `${geoServerBaseUrl}/geoserver/wms?` + + "REQUEST=GetLegendGraphic&" + + "VERSION=1.0.0&" + + "FORMAT=image/png&" + + "WIDTH=20&" + + "HEIGHT=20&" + + "LEGEND_OPTIONS=forceLabels:on;fontAntiAliasing:true&" + // Added anti-aliasing `LAYER=${encodeURIComponent(cleanLayerName)}&` + - 'TRANSPARENT=true&' + // Make background transparent - 'SCALE=0.5&' + // Adjust scale if needed - 'STYLE='; // Empty style parameter + "TRANSPARENT=true&" + // Make background transparent + "SCALE=0.5&" + // Adjust scale if needed + "STYLE=" + ); // Empty style parameter } diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index e48a831..82b2526 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -46,7 +46,7 @@ const matrixIds3857 = Array.from({ length: 19 }, (_, z) => `EPSG:3857:${z}`); function Newmap() { const mapRef = useRef(); const mapElement = useRef(null); - const [wmtsLayers, setWmtsLayers] = useState([]); + const [wmtsLayers, setWmtsLayers] = useState([]); const [activeLayerName, setActiveLayerName] = useState(null); // Single useEffect for layer fetching @@ -73,59 +73,79 @@ function Newmap() { useEffect(() => { if (!mapElement.current || !wmtsLayers.length) return; - console.log("Initializing map with layers:", wmtsLayers); + console.log('Creating map with layers:', wmtsLayers); - // Create dynamic WMTS layers - const dynamicLayers = wmtsLayers - .map((layer: any) => { - const projectionToUse = getProjection(layer.supportedCRS); - if (!projectionToUse) { - console.warn(`Projection ${layer.supportedCRS} not found for layer ${layer.name}`); - return null; - } + // Group layers by their group membership + const layersByGroup = wmtsLayers.reduce((groups: Record, layer: any) => { + const groupTitle = layer.group?.groupTitle || 'Ungrouped'; + if (!groups[groupTitle]) { + groups[groupTitle] = []; + } + groups[groupTitle].push(layer); + return groups; + }, {}); - return new TileLayer({ - properties: { - title: layer.title, - type: 'overlay' - }, - visible: false, - source: new WMTS({ - url: `${geoServerBaseUrl}/geoserver/gwc/service/wmts`, - layer: layer.name, - matrixSet: layer.matrixSet, - format: "image/png", - projection: projectionToUse, - tileGrid: new WMTSTileGrid({ - origin: [-180.0, 90.0], - resolutions: [ - 0.703125, 0.3515625, 0.17578125, 0.087890625, - 0.0439453125, 0.02197265625, 0.010986328125, - 0.0054931640625, 0.00274658203125, 0.001373291015625, - 0.0006866455078125, 0.00034332275390625, - 0.000171661376953125, 0.0000858306884765625 - ], - matrixIds: Array.from({ length: 14 }, (_, i) => `EPSG:4326:${i}`), - tileSize: [256, 256], - extent: [-180.0, -90.0, 180.0, 90.0] + // Create layer groups + const dynamicGroups = Object.entries(layersByGroup).map(([groupTitle, groupLayers]) => { + const layers = groupLayers + .map(layer => { + const projectionToUse = getProjection(layer.supportedCRS); + if (!projectionToUse) { + console.warn(`Projection ${layer.supportedCRS} not found for layer ${layer.name}`); + return null; + } + + return new TileLayer({ + properties: { + title: layer.title, + type: 'overlay' + }, + visible: false, + source: new WMTS({ + url: `${geoServerBaseUrl}/geoserver/gwc/service/wmts`, + layer: layer.name, + matrixSet: layer.matrixSet, + format: "image/png", + projection: projectionToUse, + tileGrid: new WMTSTileGrid({ + origin: [-180.0, 90.0], + resolutions: [ + 0.703125, 0.3515625, 0.17578125, 0.087890625, + 0.0439453125, 0.02197265625, 0.010986328125, + 0.0054931640625, 0.00274658203125, 0.001373291015625, + 0.0006866455078125, 0.00034332275390625, + 0.000171661376953125, 0.0000858306884765625 + ], + matrixIds: Array.from({ length: 14 }, (_, i) => `EPSG:4326:${i}`), + tileSize: [256, 256], + extent: [-180.0, -90.0, 180.0, 90.0] + }), + style: "", + wrapX: true, + tileLoadFunction: (tile: any, src: string) => { + console.log('Loading tile from:', src); + const img = tile.getImage(); + img.onerror = () => { + console.error('Tile load error:', src); + }; + img.onload = () => { + console.log('Tile loaded successfully:', src); + }; + img.src = src; + } }), - style: "", - wrapX: true, - tileLoadFunction: (tile: any, src: string) => { - console.log('Loading tile from:', src); - const img = tile.getImage(); - img.onerror = () => { - console.error('Tile load error:', src); - }; - img.onload = () => { - console.log('Tile loaded successfully:', src); - }; - img.src = src; - } - }), - }); - }) - .filter(layer => layer !== null); + }); + }) + .filter(layer => layer !== null); + + return new LayerGroup({ + properties: { + title: groupTitle, + type: 'group' // Explicitly mark as a group + }, + layers: new Collection(layers), + } as LayerGroupOptions); + }); // Create base OSM layer const osmLayer = new TileLayer({ @@ -136,25 +156,18 @@ function Newmap() { } }); - // Create layer groups - const baseGroup = new LayerGroup({ - properties: { - title: 'Base Maps' - }, - layers: [osmLayer], - } as LayerGroupOptions); - - const duduGroup = new LayerGroup({ - properties: { - title: 'Dudu Layers' - }, - layers: new Collection(dynamicLayers), - } as LayerGroupOptions); - - // Initialize map + // Initialize map with grouped layers const initialMap = new OlMap({ target: mapElement.current, - layers: [baseGroup, duduGroup], + layers: [ + new LayerGroup({ + properties: { + title: 'Base Maps' + }, + layers: [osmLayer], + } as LayerGroupOptions), + ...dynamicGroups + ], view: new View({ projection: 'EPSG:4326', center: [37.9062, -1.2921], @@ -172,18 +185,23 @@ function Newmap() { } as any); initialMap.addControl(layerSwitcher); - // Add visibility listeners - dynamicLayers.forEach(layer => { - if (layer) { - layer.on('change:visible', (event: any) => { - const isVisible = event.target.getVisible(); - const layerName = event.target.get('title'); - - if (isVisible) { - setActiveLayerName(layerName); - console.log(`Layer ${layerName} is now visible`); - } else if (activeLayerName === layerName) { - setActiveLayerName(null); + // Add visibility listeners with proper type checking + dynamicGroups.forEach(group => { + const groupLayers = group.getLayers(); // Get layers collection + if (groupLayers) { + groupLayers.forEach((layer: any) => { + if (layer) { + layer.on('change:visible', (event: any) => { + const isVisible = event.target.getVisible(); + const layerName = event.target.get('title'); + + if (isVisible) { + setActiveLayerName(layerName); + console.log(`Layer ${layerName} is now visible`); + } else if (activeLayerName === layerName) { + setActiveLayerName(null); + } + }); } }); } From ce80b79aecf145c9001af095ea318b553236f9e6 Mon Sep 17 00:00:00 2001 From: nkiro Date: Fri, 15 Nov 2024 12:27:49 +0300 Subject: [PATCH 2/9] groups --- src/api/requests.ts | 69 ++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/src/api/requests.ts b/src/api/requests.ts index 2eda5a3..819c66e 100644 --- a/src/api/requests.ts +++ b/src/api/requests.ts @@ -610,56 +610,43 @@ export async function fetchWMTSCapabilities(): Promise { ); console.log( - `Processing layer: ${layer.name} -> Normalized layer name: ${normalizedLayerName}` + `Processing layer: ${layer.name} -> Normalized name: ${normalizedLayerName}`, + `Found in group map: ${layerToGroupMap.has(normalizedLayerName)}` ); - // const layerName = layer.name.includes(":") - // ? layer.name.split(":")[1] - // : layer.name - //check if the layer belongs to any group + const group = layerToGroupMap.get(normalizedLayerName); - console.log(`Group for ${layer.name}:`, group); - //if no group found , set it to "Ungrouped" - if (!group) { - return { - ...layer, - group: { - groupName: "Ungrouped", - groupTitle: "Ungrouped Layers", - }, - }; + + // Only mark as ungrouped if: + // 1. The layer is not found in any group AND + // 2. The layer name doesn't match any group names (to avoid including group containers) + const isGroupContainer = layerGroups.some(g => + normalizeName(g.name) === normalizedLayerName + ); + + if (isGroupContainer) { + console.log(`Skipping group container: ${layer.name}`); + return null; } - //check if the group itself is a layer group - //skip addiing it to ungroupped if its part of a group - if (group.groupName && group.groupTitle) { - return { - ...layer, - group, - }; - } else { - //if no group found, set it to "Ungrouped" - //also if it is not a group itself + + if (group) { return { ...layer, group: { - groupName: "Ungrouped", - groupTitle: "Ungrouped Layers", + groupName: group.groupName, + groupTitle: group.groupTitle, }, }; } - }); - // Filter out: - // 1. Layers that mistakenly ended up in "Ungrouped" when they belong to a group - // 2. Layer groups themselves that are showing in "Ungrouped" - const finalEnrichedLayers = enrichedLayers.filter((layer: WMTSLayer) => { - // 1. Remove layers that should belong to a group, not "Ungrouped" - const isLayerInGroup = layerToGroupMap.has(normalizeName(layer.name)); - - // 2. Remove layer groups from the ungrouped list (i.e., layers that are not "Ungrouped") - const isLayerGroup = !isLayerInGroup && layer.group?.groupName === "Ungrouped"; - - // Only include layers if they have a valid group or they are genuinely ungrouped layers - return !(isLayerGroup); - }); + + return { + ...layer, + group: { + groupName: "Ungrouped", + groupTitle: "Ungrouped Layers", + }, + }; + }) + .filter((layer): layer is WMTSLayer => layer !== null); console.log("Final enriched layers:", enrichedLayers); return enrichedLayers; From 7be964c2252b809be1fbb2b7769881c2eabfbcd2 Mon Sep 17 00:00:00 2001 From: nkiro Date: Fri, 15 Nov 2024 12:30:44 +0300 Subject: [PATCH 3/9] image --- src/components/shared/navbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/shared/navbar.tsx b/src/components/shared/navbar.tsx index b8d3370..8db0b6e 100644 --- a/src/components/shared/navbar.tsx +++ b/src/components/shared/navbar.tsx @@ -28,7 +28,7 @@ const Navbar: React.FC = () => { Dudu Mapper logo From 4edcaeac538877a4d7d59e7a6d2456346f2c2ccc Mon Sep 17 00:00:00 2001 From: Lucy okoth Date: Fri, 13 Dec 2024 18:54:48 +0300 Subject: [PATCH 4/9] styled drawer --- src/api/requests.ts | 78 ++--- src/app/page.tsx | 34 +- src/components/map/DrawerComponent.tsx | 353 +++++++++++++------- src/components/map/Map.tsx | 432 ++++++++++++++++++------- src/components/map/MapBase.tsx | 215 ++++++++++++ src/components/map/MapWrapper.tsx | 32 ++ src/components/map/map_drawer.css | 51 ++- src/components/shared/DrawerComp.tsx | 12 +- src/components/shared/navbar.tsx | 12 +- 9 files changed, 912 insertions(+), 307 deletions(-) create mode 100644 src/components/map/MapBase.tsx create mode 100644 src/components/map/MapWrapper.tsx diff --git a/src/api/requests.ts b/src/api/requests.ts index 819c66e..554de20 100644 --- a/src/api/requests.ts +++ b/src/api/requests.ts @@ -587,7 +587,10 @@ export async function fetchWMTSCapabilities(): Promise { } // Create a map of normalized layer to group - const layerToGroupMap = new Map(); + const layerToGroupMap = new Map< + string, + { groupName: string; groupTitle: string } + >(); if (layerGroups && layerGroups.length > 0) { layerGroups.forEach((group: LayerGroup) => { @@ -604,49 +607,50 @@ export async function fetchWMTSCapabilities(): Promise { } // Enrich WMTS layers with group information - const enrichedLayers = wmtsLayers.map((layer: WMTSLayer) => { - const normalizedLayerName = normalizeName( - layer.name.includes(":") ? layer.name.split(":")[1] : layer.name - ); - - console.log( - `Processing layer: ${layer.name} -> Normalized name: ${normalizedLayerName}`, - `Found in group map: ${layerToGroupMap.has(normalizedLayerName)}` - ); - - const group = layerToGroupMap.get(normalizedLayerName); - - // Only mark as ungrouped if: - // 1. The layer is not found in any group AND - // 2. The layer name doesn't match any group names (to avoid including group containers) - const isGroupContainer = layerGroups.some(g => - normalizeName(g.name) === normalizedLayerName - ); + const enrichedLayers = wmtsLayers + .map((layer: WMTSLayer) => { + const normalizedLayerName = normalizeName( + layer.name.includes(":") ? layer.name.split(":")[1] : layer.name + ); + + console.log( + `Processing layer: ${layer.name} -> Normalized name: ${normalizedLayerName}`, + `Found in group map: ${layerToGroupMap.has(normalizedLayerName)}` + ); + + const group = layerToGroupMap.get(normalizedLayerName); + + // Only mark as ungrouped if: + // 1. The layer is not found in any group AND + // 2. The layer name doesn't match any group names (to avoid including group containers) + const isGroupContainer = layerGroups.some( + (g) => normalizeName(g.name) === normalizedLayerName + ); + + if (isGroupContainer) { + console.log(`Skipping group container: ${layer.name}`); + return null; + } - if (isGroupContainer) { - console.log(`Skipping group container: ${layer.name}`); - return null; - } + if (group) { + return { + ...layer, + group: { + groupName: group.groupName, + groupTitle: group.groupTitle, + }, + }; + } - if (group) { return { ...layer, group: { - groupName: group.groupName, - groupTitle: group.groupTitle, + groupName: "Ungrouped", + groupTitle: "Ungrouped Layers", }, }; - } - - return { - ...layer, - group: { - groupName: "Ungrouped", - groupTitle: "Ungrouped Layers", - }, - }; - }) - .filter((layer): layer is WMTSLayer => layer !== null); + }) + .filter((layer): layer is WMTSLayer => layer !== null); console.log("Final enriched layers:", enrichedLayers); return enrichedLayers; diff --git a/src/app/page.tsx b/src/app/page.tsx index e996ed5..f8e638a 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,12 +1,30 @@ +import Navbar from "@/components/shared/navbar"; import Newmap from "../components/map/Map"; export default function Home() { - return ( -
- {/**/} -
- -
-
- ); + return ( +
+ +
+ +
+
+ ); } + +// import Newmap from "../components/map/Map"; + +// export default function Home() { +// return ( +//
+// {/**/} +//
+// +//
+//
+// ); +// } diff --git a/src/components/map/DrawerComponent.tsx b/src/components/map/DrawerComponent.tsx index ad5e3c6..4bdbb9a 100644 --- a/src/components/map/DrawerComponent.tsx +++ b/src/components/map/DrawerComponent.tsx @@ -1,158 +1,277 @@ import React from "react"; +import { styled, useTheme, Theme, CSSObject } from "@mui/material/styles"; import Box from "@mui/material/Box"; -import Drawer from "@mui/material/Drawer"; +import MuiDrawer from "@mui/material/Drawer"; +import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar"; +import Toolbar from "@mui/material/Toolbar"; import List from "@mui/material/List"; -import ListItem from "@mui/material/ListItem"; -import Tooltip from "@mui/material/Tooltip"; +import CssBaseline from "@mui/material/CssBaseline"; +import Typography from "@mui/material/Typography"; +import Divider from "@mui/material/Divider"; import IconButton from "@mui/material/IconButton"; -import "../map/map_drawer.css"; -import OpenFilterButton from "../filters/OpenFilterButton"; +import MenuIcon from "@mui/icons-material/Menu"; +import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; +import ChevronRightIcon from "@mui/icons-material/ChevronRight"; +import ListItem from "@mui/material/ListItem"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import PersonIcon from "@mui/icons-material/Person"; import HelpIcon from "@mui/icons-material/Help"; import PrintIcon from "@mui/icons-material/Print"; import DownloadIcon from "@mui/icons-material/Download"; +import OpenFilterButton from "../filters/OpenFilterButton"; +import Newmap from "./Map"; // Import the OpenLayers map component +import dynamic from "next/dynamic"; + +// const drawerWidth = 240; + +const MapWrapper = dynamic(() => import("./MapWrapper"), { + ssr: false, + loading: () =>

Loading map...

, +}); + +const drawerWidth = 240; // Define drawerWidth here + +const openedMixin = (theme: Theme): CSSObject => ({ + width: drawerWidth, + transition: theme.transitions.create("width", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + overflowX: "hidden", +}); + +// const closedMixin = (theme: Theme): CSSObject => ({ +// transition: theme.transitions.create("width", { +// easing: theme.transitions.easing.sharp, +// duration: theme.transitions.duration.enteringScreen, +// }), +// overflowX: "hidden", +// }); -interface DrawerProps { +const closedMixin = (theme: Theme): CSSObject => ({ + transition: theme.transitions.create("width", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + overflowX: "hidden", + width: `calc(${theme.spacing(7)} + 1px)`, + [theme.breakpoints.up("sm")]: { + width: `calc(${theme.spacing(8)} + 1px)`, + }, +}); + +const DrawerHeader = styled("div")(({ theme }) => ({ + display: "flex", + alignItems: "center", + justifyContent: "flex-end", + padding: theme.spacing(0, 1), + ...theme.mixins.toolbar, +})); + +interface AppBarProps extends MuiAppBarProps { + open?: boolean; // state +} + +const AppBar = styled(MuiAppBar, { + shouldForwardProp: (prop) => prop !== "open", +})(({ theme, open }) => ({ + zIndex: theme.zIndex.drawer + 1, + transition: theme.transitions.create(["width", "margin"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + ...(open && { + marginLeft: drawerWidth, + width: `calc(100% - ${drawerWidth}px)`, + transition: theme.transitions.create(["width", "margin"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + }), +})); + +const Drawer = styled(MuiDrawer, { + shouldForwardProp: (prop) => prop !== "open", +})(({ theme, open }) => ({ + width: drawerWidth, + flexShrink: 0, + whiteSpace: "nowrap", + boxSizing: "border-box", + ...(open && { + ...openedMixin(theme), + "& .MuiDrawer-paper": openedMixin(theme), + }), + ...(!open && { + ...closedMixin(theme), + "& .MuiDrawer-paper": closedMixin(theme), + }), +})); + +interface DrawerComponentProps { sidebarOpen: boolean; toggleSidebar: () => void; filterOpen: boolean; - setFilterOpen: (value: boolean) => void; - printToScale: () => void; handleDownloadClick: () => void; } -const DrawerComponent: React.FC = ({ +export function DrawerComponent({ sidebarOpen, toggleSidebar, filterOpen, setFilterOpen, printToScale, handleDownloadClick, -}) => { +}: DrawerComponentProps) { + const theme = useTheme(); + const [open, setOpen] = React.useState(sidebarOpen); + + const handleDrawerOpen = () => { + setOpen(true); + toggleSidebar(); + }; + + const handleDrawerClose = () => { + setOpen(false); + toggleSidebar(); + }; + return ( - - {sidebarOpen && ( -
- )} + + + + + + + + + Map Dashboard + + + -
- - - - - - - {/* OpenFilterButton and OccurrenceFilter */} - -
-
- setFilterOpen(!filterOpen)} - /> -
-
-
- - {/* Print map button */} - -
- - - - - - + + + {theme.direction === "rtl" ? ( + + ) : ( + + )} + + + + + +
+
+ setFilterOpen(!filterOpen)} + />
- +
+
+ + +
+ + + +
+
- {/*Download */} +
-
- - - - - - - -
+ + +
- {/* Profile */} +
+ +
-
- - - - - - - -
+ + +
- {/* Help */} +
+ +
-
- - - - - - - -
+ + +
-
-
+
+
+ + + + + + {/* */} + {/* */} + {/* */} + {/* */} ); -}; +} export default DrawerComponent; diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index 82b2526..d80f4f0 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -1,5 +1,28 @@ "use client"; import React, { useEffect, useRef, useState } from "react"; +import { styled, useTheme } from "@mui/material/styles"; +import Box from "@mui/material/Box"; +import Drawer from "@mui/material/Drawer"; +import CssBaseline from "@mui/material/CssBaseline"; +import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar"; +import Toolbar from "@mui/material/Toolbar"; +import List from "@mui/material/List"; +import Typography from "@mui/material/Typography"; +import Divider from "@mui/material/Divider"; +import IconButton from "@mui/material/IconButton"; +import MenuIcon from "@mui/icons-material/Menu"; +import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; +import ChevronRightIcon from "@mui/icons-material/ChevronRight"; +import ListItem from "@mui/material/ListItem"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import InboxIcon from "@mui/icons-material/MoveToInbox"; +import MailIcon from "@mui/icons-material/Mail"; +import { useMediaQuery } from "@mui/material"; +// import DrawerComp from "./DrawerComp"; +import Link from "next/link"; // Ensure this is imported correctly + import { Map as OlMap, Tile, View } from "ol"; import "ol/ol.css"; import "ol-ext/control/LayerSwitcher.css"; @@ -7,49 +30,106 @@ import LayerSwitcher from "ol-ext/control/LayerSwitcher"; import LayerGroup from "ol/layer/Group"; import TileLayer from "ol/layer/Tile"; import WMTS from "ol/source/WMTS"; -import WMTSCapabilities from "ol/format/WMTSCapabilities"; import WMTSTileGrid from "ol/tilegrid/WMTS"; -import { fromLonLat } from "ol/proj"; -import { getTopLeft, getWidth } from "ol/extent"; import Collection from "ol/Collection"; import OSM from "ol/source/OSM"; import { get as getProjection } from "ol/proj"; -import { - geoServerBaseUrl, - getBasemapOverlaysLayersArray, - fetchWMTSCapabilities, -} from "@/api/requests"; // Adjust import path as needed -import Legend from "./Legend"; // Import the Legend component +import { geoServerBaseUrl, fetchWMTSCapabilities } from "@/api/requests"; +import { fromLonLat } from "ol/proj"; +import { getTopLeft, getWidth } from "ol/extent"; import { Options as LayerGroupOptions } from "ol/layer/Group"; +import Legend from "./Legend"; -// Add these constants at the top of the file after imports -const projection4326 = getProjection("EPSG:4326"); -const projection3857 = getProjection("EPSG:3857"); +const drawerWidth = 240; + +// Styled Main component for map container -// Calculate resolutions and matrix IDs for EPSG:4326 +const Main = styled("main", { shouldForwardProp: (prop) => prop !== "open" })<{ + open?: boolean; +}>(({ theme, open }) => ({ + flexGrow: 1, + transition: theme.transitions.create("margin", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + marginLeft: `-${drawerWidth}px`, + ...(open && { + transition: theme.transitions.create("margin", { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + marginLeft: 0, + }), +})); + +// AppBar styling +interface AppBarProps extends MuiAppBarProps { + open?: boolean; +} +const AppBar = styled(MuiAppBar, { + shouldForwardProp: (prop) => prop !== "open", +})(({ theme, open }) => ({ + backgroundColor: "white", + transition: theme.transitions.create(["margin", "width"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + ...(open && { + width: `calc(100% - ${drawerWidth}px)`, + marginLeft: `${drawerWidth}px`, + transition: theme.transitions.create(["margin", "width"], { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + }), +})); + +// Drawer Header +const DrawerHeader = styled("div")(({ theme }) => ({ + display: "flex", + alignItems: "center", + padding: theme.spacing(0, 1), + ...theme.mixins.toolbar, + justifyContent: "flex-end", +})); + +// Projection and matrix calculations +const projection4326 = getProjection("EPSG:4326"); const projectionExtent4326 = projection4326?.getExtent(); -const size4326 = projectionExtent4326 ? getWidth(projectionExtent4326) / 256 : 0; +const size4326 = projectionExtent4326 + ? getWidth(projectionExtent4326) / 256 + : 0; const resolutions4326 = projectionExtent4326 ? Array.from({ length: 19 }, (_, z) => size4326 / Math.pow(2, z)) : []; const matrixIds4326 = Array.from({ length: 19 }, (_, z) => `EPSG:4326:${z}`); -// Calculate resolutions and matrix IDs for EPSG:3857 -const projectionExtent3857 = projection3857?.getExtent(); -const size3857 = projectionExtent3857 ? getWidth(projectionExtent3857) / 256 : 0; -const resolutions3857 = Array.from( - { length: 19 }, - (_, z) => size3857 / Math.pow(2, z) -); -const matrixIds3857 = Array.from({ length: 19 }, (_, z) => `EPSG:3857:${z}`); - -function Newmap() { +const Newmap = () => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); const mapRef = useRef(); const mapElement = useRef(null); const [wmtsLayers, setWmtsLayers] = useState([]); const [activeLayerName, setActiveLayerName] = useState(null); + const [open, setOpen] = useState(false); + + const navMenuItems = [ + + About + , + + Contact + , + ]; + + const handleDrawerOpen = () => { + setOpen(true); + }; + + const handleDrawerClose = () => { + setOpen(false); + }; - // Single useEffect for layer fetching useEffect(() => { const fetchLayers = async () => { if (!geoServerBaseUrl) { @@ -59,7 +139,7 @@ function Newmap() { try { const layers = await fetchWMTSCapabilities(); - console.log('Fetched WMTS layers:', layers); + console.log("Fetched WMTS layers:", layers); setWmtsLayers(layers); } catch (error) { console.error("Error fetching capabilities:", error); @@ -69,132 +149,142 @@ function Newmap() { fetchLayers(); }, []); - // Initialize map with layers + // Update map size when drawer opens/closes + useEffect(() => { + if (mapRef.current) { + mapRef.current.updateSize(); + } + }, [open]); + useEffect(() => { if (!mapElement.current || !wmtsLayers.length) return; - console.log('Creating map with layers:', wmtsLayers); + console.log("Creating map with layers:", wmtsLayers); - // Group layers by their group membership - const layersByGroup = wmtsLayers.reduce((groups: Record, layer: any) => { - const groupTitle = layer.group?.groupTitle || 'Ungrouped'; - if (!groups[groupTitle]) { - groups[groupTitle] = []; - } - groups[groupTitle].push(layer); - return groups; - }, {}); - - // Create layer groups - const dynamicGroups = Object.entries(layersByGroup).map(([groupTitle, groupLayers]) => { - const layers = groupLayers - .map(layer => { - const projectionToUse = getProjection(layer.supportedCRS); - if (!projectionToUse) { - console.warn(`Projection ${layer.supportedCRS} not found for layer ${layer.name}`); - return null; - } + const layersByGroup = wmtsLayers.reduce( + (groups: Record, layer: any) => { + const groupTitle = layer.group?.groupTitle || "Ungrouped"; + if (!groups[groupTitle]) { + groups[groupTitle] = []; + } + groups[groupTitle].push(layer); + return groups; + }, + {} + ); + + const dynamicGroups = Object.entries(layersByGroup).map( + ([groupTitle, groupLayers]) => { + const layers = groupLayers + .map((layer) => { + const projectionToUse = getProjection(layer.supportedCRS); + if (!projectionToUse) { + console.warn( + `Projection ${layer.supportedCRS} not found for layer ${layer.name}` + ); + return null; + } - return new TileLayer({ - properties: { - title: layer.title, - type: 'overlay' - }, - visible: false, - source: new WMTS({ - url: `${geoServerBaseUrl}/geoserver/gwc/service/wmts`, - layer: layer.name, - matrixSet: layer.matrixSet, - format: "image/png", - projection: projectionToUse, - tileGrid: new WMTSTileGrid({ - origin: [-180.0, 90.0], - resolutions: [ - 0.703125, 0.3515625, 0.17578125, 0.087890625, - 0.0439453125, 0.02197265625, 0.010986328125, - 0.0054931640625, 0.00274658203125, 0.001373291015625, - 0.0006866455078125, 0.00034332275390625, - 0.000171661376953125, 0.0000858306884765625 - ], - matrixIds: Array.from({ length: 14 }, (_, i) => `EPSG:4326:${i}`), - tileSize: [256, 256], - extent: [-180.0, -90.0, 180.0, 90.0] + return new TileLayer({ + properties: { + title: layer.title, + type: "overlay", + }, + visible: false, + source: new WMTS({ + url: `${geoServerBaseUrl}/geoserver/gwc/service/wmts`, + layer: layer.name, + matrixSet: layer.matrixSet, + format: "image/png", + projection: projectionToUse, + tileGrid: new WMTSTileGrid({ + origin: [-180.0, 90.0], + resolutions: [ + 0.703125, 0.3515625, 0.17578125, 0.087890625, 0.0439453125, + 0.02197265625, 0.010986328125, 0.0054931640625, + 0.00274658203125, 0.001373291015625, 0.0006866455078125, + 0.00034332275390625, 0.000171661376953125, + 0.0000858306884765625, + ], + matrixIds: Array.from( + { length: 14 }, + (_, i) => `EPSG:4326:${i}` + ), + tileSize: [256, 256], + extent: [-180.0, -90.0, 180.0, 90.0], + }), + style: "", + wrapX: true, + tileLoadFunction: (tile: any, src: string) => { + console.log("Loading tile from:", src); + const img = tile.getImage(); + img.onerror = () => { + console.error("Tile load error:", src); + }; + img.onload = () => { + console.log("Tile loaded successfully:", src); + }; + img.src = src; + }, }), - style: "", - wrapX: true, - tileLoadFunction: (tile: any, src: string) => { - console.log('Loading tile from:', src); - const img = tile.getImage(); - img.onerror = () => { - console.error('Tile load error:', src); - }; - img.onload = () => { - console.log('Tile loaded successfully:', src); - }; - img.src = src; - } - }), - }); - }) - .filter(layer => layer !== null); - - return new LayerGroup({ - properties: { - title: groupTitle, - type: 'group' // Explicitly mark as a group - }, - layers: new Collection(layers), - } as LayerGroupOptions); - }); + }); + }) + .filter((layer) => layer !== null); + + return new LayerGroup({ + properties: { + title: groupTitle, + type: "group", + }, + layers: new Collection(layers), + }); + } + ); - // Create base OSM layer const osmLayer = new TileLayer({ source: new OSM(), properties: { - title: 'OpenStreetMap', - type: 'base' - } + title: "OpenStreetMap", + type: "base", + }, }); - // Initialize map with grouped layers const initialMap = new OlMap({ target: mapElement.current, layers: [ new LayerGroup({ properties: { - title: 'Base Maps' + title: "Base Maps", }, layers: [osmLayer], - } as LayerGroupOptions), - ...dynamicGroups + }), + ...dynamicGroups, ], view: new View({ - projection: 'EPSG:4326', + projection: "EPSG:4326", center: [37.9062, -1.2921], zoom: 6, minZoom: 0, maxZoom: 13, - extent: [-180.0, -90.0, 180.0, 90.0] + extent: [-180.0, -90.0, 180.0, 90.0], }), }); - // Add layer switcher const layerSwitcher = new LayerSwitcher({ startActive: true, groupSelectStyle: "children", } as any); initialMap.addControl(layerSwitcher); - // Add visibility listeners with proper type checking - dynamicGroups.forEach(group => { - const groupLayers = group.getLayers(); // Get layers collection + dynamicGroups.forEach((group) => { + const groupLayers = group.getLayers(); if (groupLayers) { groupLayers.forEach((layer: any) => { if (layer) { - layer.on('change:visible', (event: any) => { + layer.on("change:visible", (event: any) => { const isVisible = event.target.getVisible(); - const layerName = event.target.get('title'); - + const layerName = event.target.get("title"); + if (isVisible) { setActiveLayerName(layerName); console.log(`Layer ${layerName} is now visible`); @@ -215,16 +305,110 @@ function Newmap() { }, [wmtsLayers]); return ( -
-
- -
+ + + + + + + + + + + Dudu Mapper logo + + + + + Dudumapper + + + + + + + {theme.direction === "ltr" ? ( + + ) : ( + + )} + + + + + + Dudu Mapper logo + + + + + + {["Layer Controls", "Base Maps", "Overlays"].map((text, index) => ( + + + + {index % 2 === 0 ? : } + + + + + ))} + + +
+ {/* This pushes the content below the app bar */} +
+ +
+
); -} +}; export default Newmap; diff --git a/src/components/map/MapBase.tsx b/src/components/map/MapBase.tsx new file mode 100644 index 0000000..4074b71 --- /dev/null +++ b/src/components/map/MapBase.tsx @@ -0,0 +1,215 @@ +// "use client"; + +// import React, { useEffect, useRef, useState } from "react"; +// import { Map as OlMap, View } from "ol"; +// import "ol/ol.css"; +// import "ol-ext/control/LayerSwitcher.css"; +// import LayerSwitcher from "ol-ext/control/LayerSwitcher"; +// import LayerGroup from "ol/layer/Group"; +// import TileLayer from "ol/layer/Tile"; +// import WMTS from "ol/source/WMTS"; +// import WMTSTileGrid from "ol/tilegrid/WMTS"; +// import { Projection } from "ol/proj"; +// import Collection from "ol/Collection"; +// import OSM from "ol/source/OSM"; +// import { geoServerBaseUrl, fetchWMTSCapabilities } from "@/api/requests"; +// import Legend from "./Legend"; +// import DrawerComponent from "./DrawerComponent"; +// import { Options as LayerGroupOptions } from "ol/layer/Group"; +// import { get as getProjection } from "ol/proj"; + +// function MapBase() { +// const mapRef = useRef(); +// const mapElement = useRef(null); +// const [wmtsLayers, setWmtsLayers] = useState([]); +// const [activeLayerName, setActiveLayerName] = useState(null); +// const [sidebarOpen, setSidebarOpen] = useState(true); +// const [filterOpen, setFilterOpen] = useState(false); + +// const toggleSidebar = () => setSidebarOpen(!sidebarOpen); +// const printToScale = () => console.log("Print map"); +// const handleDownloadClick = () => console.log("Download map"); + +// useEffect(() => { +// const fetchLayers = async () => { +// if (!geoServerBaseUrl) { +// console.error("GeoServer base URL is not set"); +// return; +// } + +// try { +// const layers = await fetchWMTSCapabilities(); +// setWmtsLayers(layers); +// } catch (error) { +// console.error("Error fetching capabilities:", error); +// } +// }; + +// fetchLayers(); +// }, []); + +// useEffect(() => { +// if (!mapElement.current || !wmtsLayers.length || mapRef.current) return; + +// const layersByGroup = wmtsLayers.reduce( +// (groups: Record, layer: any) => { +// const groupTitle = layer.group?.groupTitle || "Ungrouped"; +// if (!groups[groupTitle]) groups[groupTitle] = []; +// groups[groupTitle].push(layer); +// return groups; +// }, +// {} +// ); + +// const dynamicGroups = Object.entries(layersByGroup).map( +// ([groupTitle, groupLayers]) => { +// const layers = groupLayers +// .map((layer) => { +// const projectionToUse = getProjection(layer.supportedCRS); +// if (!projectionToUse) { +// console.warn( +// `Projection ${layer.supportedCRS} not found for layer ${layer.name}` +// ); +// return null; +// } + +// return new TileLayer({ +// properties: { +// title: layer.title, +// type: "overlay", +// }, +// visible: false, +// source: new WMTS({ +// url: `${geoServerBaseUrl}/geoserver/gwc/service/wmts`, +// layer: layer.name, +// matrixSet: layer.matrixSet, +// format: "image/png", +// projection: projectionToUse, +// tileGrid: new WMTSTileGrid({ +// origin: [-180.0, 90.0], +// resolutions: [ +// 0.703125, 0.3515625, 0.17578125, 0.087890625, 0.0439453125, +// 0.02197265625, 0.010986328125, 0.0054931640625, +// 0.00274658203125, 0.001373291015625, 0.0006866455078125, +// 0.00034332275390625, 0.000171661376953125, +// 0.0000858306884765625, +// ], +// matrixIds: Array.from( +// { length: 14 }, +// (_, i) => `EPSG:4326:${i}` +// ), +// tileSize: [256, 256], +// extent: [-180.0, -90.0, 180.0, 90.0], +// }), +// style: "", +// wrapX: true, +// }), +// }); +// }) +// .filter((layer): layer is TileLayer => layer !== null); + +// return new LayerGroup({ +// properties: { +// title: groupTitle, +// type: "group", +// }, +// layers: new Collection(layers), +// } as LayerGroupOptions); +// } +// ); + +// const osmLayer = new TileLayer({ +// source: new OSM(), +// properties: { +// title: "OpenStreetMap", +// type: "base", +// }, +// }); + +// const map = new OlMap({ +// target: mapElement.current, +// layers: [ +// new LayerGroup({ +// properties: { +// title: "Base Maps", +// }, +// layers: [osmLayer], +// } as LayerGroupOptions), +// ...dynamicGroups, +// ], +// view: new View({ +// projection: "EPSG:4326", +// center: [37.9062, -1.2921], +// zoom: 6, +// minZoom: 0, +// maxZoom: 13, +// extent: [-180.0, -90.0, 180.0, 90.0], +// }), +// }); + +// const layerSwitcher = new (LayerSwitcher as any)({ +// startActive: true, +// groupSelectStyle: "children", +// }); +// map.addControl(layerSwitcher); + +// dynamicGroups.forEach((group) => { +// const groupLayers = group.getLayers(); +// if (groupLayers) { +// groupLayers.forEach((layer: any) => { +// if (layer) { +// layer.on("change:visible", (event: any) => { +// const isVisible = event.target.getVisible(); +// const layerName = event.target.get("title"); +// if (isVisible) { +// setActiveLayerName(layerName); +// } else if (activeLayerName === layerName) { +// setActiveLayerName(null); +// } +// }); +// } +// }); +// } +// }); + +// mapRef.current = map; + +// return () => { +// map.setTarget(undefined); +// }; +// }, [wmtsLayers, activeLayerName]); + +// return ( +//
+// +//
+//
+// +//
+//
+// ); +// } + +// export default MapBase; diff --git a/src/components/map/MapWrapper.tsx b/src/components/map/MapWrapper.tsx new file mode 100644 index 0000000..94f6c70 --- /dev/null +++ b/src/components/map/MapWrapper.tsx @@ -0,0 +1,32 @@ +// "use client"; +// import dynamic from "next/dynamic"; +// import React from "react"; + +// const NewmapNoSSR = dynamic(() => import("./Map"), { +// ssr: false, +// loading: () =>

Loading map...

, +// }); + +// export default function MapWrapper() { +// return ; +// } + +// MapWrapper.tsx +import React from "react"; +import NewMap from "./Map"; + +const MapWrapper = () => { + return ( +
+ +
+ ); +}; + +export default MapWrapper; diff --git a/src/components/map/map_drawer.css b/src/components/map/map_drawer.css index e6c4441..527dec4 100644 --- a/src/components/map/map_drawer.css +++ b/src/components/map/map_drawer.css @@ -11,16 +11,16 @@ /* } */ /* drawer.css */ - +/* .drawer { - width: 55px; /* Adjust the width of the drawer as needed */ + width: 55px; overflow: auto; - transition: width 0.2s ease; /* Synchronize transition duration with occurrence popup */ - z-index: 10; /* Ensure the drawer appears below occurrence popup if they overlap */ + transition: width 0.2s ease; + z-index: 10; } .drawer.open { - width: 300px; /* Adjusted width when the drawer is open */ + width: 300px; } .drawer-overlay { @@ -29,11 +29,44 @@ left: 0; width: 100%; height: 100%; - background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent overlay */ - z-index: 9; /* Ensure the overlay appears below the drawer */ - display: none; /* Initially hidden */ + background-color: rgba(0, 0, 0, 0.5); + z-index: 9; + display: none; } + */ .drawer.open + .drawer-overlay { - display: block; /* Display overlay when the drawer is open */ + display: block; +} + +.map-layout-container { + position: relative; + overflow: hidden; +} + +.drawer { + width: 55px; + overflow: hidden; + transition: width 0.2s ease; + z-index: 10; +} + +.drawer.closed { + width: 55px; +} + +/* .drawer-overlay { + transition: opacity 0.3s ease-in-out; +} */ + +.filter-dev-button, +.print-dev-button { + display: flex; + align-items: center; +} + +.map-container { + width: 100%; + height: 100%; + position: relative; } diff --git a/src/components/shared/DrawerComp.tsx b/src/components/shared/DrawerComp.tsx index 4cda842..31fcc58 100644 --- a/src/components/shared/DrawerComp.tsx +++ b/src/components/shared/DrawerComp.tsx @@ -1,7 +1,6 @@ -"use client" -import React, { useState } from 'react'; -import { Drawer, IconButton, List } from '@mui/material'; -import MenuIcon from '@mui/icons-material/Menu'; +import React, { useState } from "react"; +import { Drawer, IconButton, List } from "@mui/material"; +import MenuIcon from "@mui/icons-material/Menu"; function DrawerComp({ navItems }: { navItems: any[] }) { const [openDrawer, setOpenDrawer] = useState(false); @@ -15,14 +14,15 @@ function DrawerComp({ navItems }: { navItems: any[] }) { onClose={() => setOpenDrawer(false)} PaperProps={{ sx: { - height: 240, + zIndex: 1301, // Ensure this drawer is on top if needed + height: "100%", // Make it full height for better usability }, }} > {navItems} setOpenDrawer(!openDrawer)} data-testid="openDrawer" > diff --git a/src/components/shared/navbar.tsx b/src/components/shared/navbar.tsx index 8db0b6e..20f1853 100644 --- a/src/components/shared/navbar.tsx +++ b/src/components/shared/navbar.tsx @@ -1,10 +1,10 @@ "use client"; import * as React from "react"; -import AppBar from "@mui/material/AppBar"; +// import AppBar from "@mui/material/AppBar"; import Box from "@mui/material/Box"; -import Toolbar from "@mui/material/Toolbar"; -import Link from "next/link"; -import DrawerComp from "./DrawerComp"; +// import Toolbar from "@mui/material/Toolbar"; +// import Link from "next/link"; +// import DrawerComp from "./DrawerComp"; import NavLink from "./navlink"; import { useMediaQuery, useTheme } from "@mui/material"; import { BASE_PATH } from "@/lib/constants"; @@ -18,7 +18,7 @@ const Navbar: React.FC = () => { return (
- @@ -43,7 +43,7 @@ const Navbar: React.FC = () => { )} - + */}
); From a8a835d24230ba28a9d695601af5085a44ffbef0 Mon Sep 17 00:00:00 2001 From: Lucy okoth Date: Fri, 10 Jan 2025 17:51:12 +0300 Subject: [PATCH 5/9] added styling to drawer --- src/components/map/DrawerComponent.tsx | 8 +- src/components/map/LayerControls.tsx | 44 +++++ src/components/map/Map.tsx | 242 +++++++++++++++++-------- 3 files changed, 210 insertions(+), 84 deletions(-) create mode 100644 src/components/map/LayerControls.tsx diff --git a/src/components/map/DrawerComponent.tsx b/src/components/map/DrawerComponent.tsx index 4bdbb9a..f031b4f 100644 --- a/src/components/map/DrawerComponent.tsx +++ b/src/components/map/DrawerComponent.tsx @@ -266,10 +266,10 @@ export function DrawerComponent({ - {/* */} - {/* */} - {/* */} - {/* */} + + + + ); } diff --git a/src/components/map/LayerControls.tsx b/src/components/map/LayerControls.tsx new file mode 100644 index 0000000..60dffb2 --- /dev/null +++ b/src/components/map/LayerControls.tsx @@ -0,0 +1,44 @@ +import { + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, +} from "@mui/material"; +import InboxIcon from "@mui/icons-material/Inbox"; +import MailIcon from "@mui/icons-material/Mail"; +import CheckIcon from "@mui/icons-material/Check"; + +interface LayerControlsProps { + activeLayerName: string; + toggleLayer: (name: string) => void; +} + +const LayerControls = ({ + activeLayerName, + toggleLayer, +}: LayerControlsProps) => { + return ( + + {[ + "Dudu:Suitability of ...", + "turkana_dec", + "Ungrouped Layers", + "Dudu:Population", + "Base Maps", + ].map((text, index) => ( + + toggleLayer(text)}> + + {index % 2 === 0 ? : } + + + {activeLayerName === text && } + + + ))} + + ); +}; + +export default LayerControls; diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index d80f4f0..d29bd92 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -38,6 +38,15 @@ import { geoServerBaseUrl, fetchWMTSCapabilities } from "@/api/requests"; import { fromLonLat } from "ol/proj"; import { getTopLeft, getWidth } from "ol/extent"; import { Options as LayerGroupOptions } from "ol/layer/Group"; +import ExpandLess from "@mui/icons-material/ExpandLess"; +import ExpandMore from "@mui/icons-material/ExpandMore"; +import { Collapse } from "@mui/material"; +import Checkbox from "@mui/material/Checkbox"; +import LayersIcon from "@mui/icons-material/Layers"; +import MapIcon from "@mui/icons-material/Map"; +import FolderIcon from "@mui/icons-material/Folder"; +import Tooltip from "@mui/material/Tooltip"; +import { alpha } from "@mui/material/styles"; import Legend from "./Legend"; const drawerWidth = 240; @@ -106,21 +115,18 @@ const matrixIds4326 = Array.from({ length: 19 }, (_, z) => `EPSG:4326:${z}`); const Newmap = () => { const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down("sm")); const mapRef = useRef(); const mapElement = useRef(null); const [wmtsLayers, setWmtsLayers] = useState([]); const [activeLayerName, setActiveLayerName] = useState(null); const [open, setOpen] = useState(false); + const [overlaysOpen, setOverlaysOpen] = useState(false); + const [layerGroups, setLayerGroups] = useState>({}); + const [expandedGroups, setExpandedGroups] = useState>( + {} + ); - const navMenuItems = [ - - About - , - - Contact - , - ]; + // Handle drawer states const handleDrawerOpen = () => { setOpen(true); @@ -130,6 +136,29 @@ const Newmap = () => { setOpen(false); }; + const handleOverlaysClick = () => setOverlaysOpen(!overlaysOpen); + + // Handle group expansion + const handleGroupClick = (groupTitle: string) => { + setExpandedGroups((prev) => ({ + ...prev, + [groupTitle]: !prev[groupTitle], + })); + }; + + // Handle layer visibility + const handleLayerToggle = (layer: any) => { + if (layer) { + const newVisibility = !layer.getVisible(); + layer.setVisible(newVisibility); + if (newVisibility) { + setActiveLayerName(layer.get("title")); + } else if (activeLayerName === layer.get("title")) { + setActiveLayerName(null); + } + } + }; + useEffect(() => { const fetchLayers = async () => { if (!geoServerBaseUrl) { @@ -139,8 +168,22 @@ const Newmap = () => { try { const layers = await fetchWMTSCapabilities(); - console.log("Fetched WMTS layers:", layers); setWmtsLayers(layers); + + // Organize layers by group + const groupedLayers = layers.reduce( + (groups: Record, layer: any) => { + const groupTitle = layer.group?.groupTitle || "Ungrouped"; + if (!groups[groupTitle]) { + groups[groupTitle] = []; + } + groups[groupTitle].push(layer); + return groups; + }, + {} + ); + + setLayerGroups(groupedLayers); } catch (error) { console.error("Error fetching capabilities:", error); } @@ -148,8 +191,6 @@ const Newmap = () => { fetchLayers(); }, []); - - // Update map size when drawer opens/closes useEffect(() => { if (mapRef.current) { mapRef.current.updateSize(); @@ -159,21 +200,7 @@ const Newmap = () => { useEffect(() => { if (!mapElement.current || !wmtsLayers.length) return; - console.log("Creating map with layers:", wmtsLayers); - - const layersByGroup = wmtsLayers.reduce( - (groups: Record, layer: any) => { - const groupTitle = layer.group?.groupTitle || "Ungrouped"; - if (!groups[groupTitle]) { - groups[groupTitle] = []; - } - groups[groupTitle].push(layer); - return groups; - }, - {} - ); - - const dynamicGroups = Object.entries(layersByGroup).map( + const dynamicGroups = Object.entries(layerGroups).map( ([groupTitle, groupLayers]) => { const layers = groupLayers .map((layer) => { @@ -215,21 +242,10 @@ const Newmap = () => { }), style: "", wrapX: true, - tileLoadFunction: (tile: any, src: string) => { - console.log("Loading tile from:", src); - const img = tile.getImage(); - img.onerror = () => { - console.error("Tile load error:", src); - }; - img.onload = () => { - console.log("Tile loaded successfully:", src); - }; - img.src = src; - }, }), }); }) - .filter((layer) => layer !== null); + .filter((layer): layer is TileLayer => layer !== null); return new LayerGroup({ properties: { @@ -270,39 +286,89 @@ const Newmap = () => { }), }); - const layerSwitcher = new LayerSwitcher({ - startActive: true, - groupSelectStyle: "children", - } as any); - initialMap.addControl(layerSwitcher); - - dynamicGroups.forEach((group) => { - const groupLayers = group.getLayers(); - if (groupLayers) { - groupLayers.forEach((layer: any) => { - if (layer) { - layer.on("change:visible", (event: any) => { - const isVisible = event.target.getVisible(); - const layerName = event.target.get("title"); - - if (isVisible) { - setActiveLayerName(layerName); - console.log(`Layer ${layerName} is now visible`); - } else if (activeLayerName === layerName) { - setActiveLayerName(null); - } - }); - } - }); - } - }); - mapRef.current = initialMap; return () => { initialMap.setTarget(undefined); }; - }, [wmtsLayers]); + }, [wmtsLayers, layerGroups]); + + const renderLayerControls = () => ( + + {Object.entries(layerGroups).map(([groupTitle, groupLayers]) => ( +
+ handleGroupClick(groupTitle)} + sx={{ + pl: 4, + py: 1, + "&:hover": { + backgroundColor: alpha(theme.palette.primary.main, 0.1), + }, + backgroundColor: expandedGroups[groupTitle] + ? alpha(theme.palette.primary.main, 0.08) + : "transparent", + }} + > + + + + + {expandedGroups[groupTitle] ? ( + + ) : ( + + )} + + + + {groupLayers.map((layer) => { + const olLayer = mapRef.current + ?.getLayers() + .getArray() + .find((l: any) => l.get("title") === groupTitle) + ?.getLayers() + .getArray() + .find((l: any) => l.get("title") === layer.title); + + const isVisible = olLayer?.getVisible() || false; + + return ( + handleLayerToggle(olLayer)} + > + + + + ); + })} + + +
+ ))} +
+ ); return ( @@ -381,30 +447,46 @@ const Newmap = () => { - {["Layer Controls", "Base Maps", "Overlays"].map((text, index) => ( - - - - {index % 2 === 0 ? : } - - - - - ))} + + + + + + + + + + + + + + + + + + + + + + + {overlaysOpen ? : } + + + {renderLayerControls()}
- {/* This pushes the content below the app bar */} +
+ />
From 4d80ec0ef4ea790b2c82ce677eb12d5ad1d046ec Mon Sep 17 00:00:00 2001 From: Lucy okoth Date: Tue, 14 Jan 2025 14:18:38 +0300 Subject: [PATCH 6/9] added dowload compnent --- src/components/map/Map.tsx | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index d29bd92..4f64d65 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -47,6 +47,9 @@ import MapIcon from "@mui/icons-material/Map"; import FolderIcon from "@mui/icons-material/Folder"; import Tooltip from "@mui/material/Tooltip"; import { alpha } from "@mui/material/styles"; +import DownloadPopup from "./DownloadPopup"; +import Button from "@mui/material/Button"; +import { Download } from "lucide-react"; import Legend from "./Legend"; const drawerWidth = 240; @@ -125,9 +128,18 @@ const Newmap = () => { const [expandedGroups, setExpandedGroups] = useState>( {} ); + const [downloadPopupOpen, setDownloadPopupOpen] = useState(false); + const [cqlFilter, setCqlFilter] = useState(null); + + const handleLayerSelection = (layer: any) => { + const filter = `layer_name='${layer.get("title")}'`; // Example filter + setCqlFilter(filter); + }; // Handle drawer states + // const [downloadPopupOpen, setDownloadPopupOpen] = useState(false); + // const [cqlFilter, setCqlFilter] = useState(null); const handleDrawerOpen = () => { setOpen(true); }; @@ -375,6 +387,7 @@ const Newmap = () => { @@ -401,6 +414,14 @@ const Newmap = () => { Dudumapper + { "& .MuiDrawer-paper": { width: drawerWidth, boxSizing: "border-box", + // position: "relative", }, }} variant="persistent" @@ -425,6 +447,7 @@ const Newmap = () => { )}
+ { {overlaysOpen ? : } + + setDownloadPopupOpen(true)}> + + + + + + {renderLayerControls()} +
{ position: "relative", }} /> + + setDownloadPopupOpen(false)} + cqlFilter={cqlFilter || ""} + /> +
From cb71a27c7691f7f3f6d66754862a3d6001ef1ff3 Mon Sep 17 00:00:00 2001 From: Lucy okoth Date: Wed, 15 Jan 2025 11:31:06 +0300 Subject: [PATCH 7/9] removed button --- src/components/map/Map.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index 4f64d65..62bbc49 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -414,14 +414,7 @@ const Newmap = () => { Dudumapper - + ? Date: Wed, 15 Jan 2025 12:44:04 +0300 Subject: [PATCH 8/9] color changes --- src/components/map/Map.tsx | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index 62bbc49..df202c1 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -51,6 +51,7 @@ import DownloadPopup from "./DownloadPopup"; import Button from "@mui/material/Button"; import { Download } from "lucide-react"; import Legend from "./Legend"; +import { green } from "@mui/material/colors"; const drawerWidth = 240; @@ -318,13 +319,15 @@ const Newmap = () => { backgroundColor: alpha(theme.palette.primary.main, 0.1), }, backgroundColor: expandedGroups[groupTitle] - ? alpha(theme.palette.primary.main, 0.08) + ? alpha(green[500], 0.1) : "transparent", }} > { primaryTypographyProps={{ fontWeight: expandedGroups[groupTitle] ? 600 : 400, color: expandedGroups[groupTitle] - ? theme.palette.primary.main - : "inherit", + ? green[500] // Green text when expanded + : "inherit", // default color otherwise }} /> {expandedGroups[groupTitle] ? ( - + ) : ( )} @@ -414,7 +417,14 @@ const Newmap = () => { Dudumapper - ? + Date: Wed, 15 Jan 2025 21:24:57 +0300 Subject: [PATCH 9/9] added download component t drawer --- src/components/map/DownloadPopup.tsx | 363 ++++++++++++++------------- src/components/map/Map.tsx | 91 +++++-- 2 files changed, 257 insertions(+), 197 deletions(-) diff --git a/src/components/map/DownloadPopup.tsx b/src/components/map/DownloadPopup.tsx index dfd4835..fa6b0a9 100644 --- a/src/components/map/DownloadPopup.tsx +++ b/src/components/map/DownloadPopup.tsx @@ -1,202 +1,209 @@ -import React, {useState} from "react"; +import React, { useState } from "react"; import { - Box, - Typography, - Grid, - FormControlLabel, - Radio, - RadioGroup, - IconButton, - Button, CircularProgress, + Box, + Typography, + Grid, + FormControlLabel, + Radio, + RadioGroup, + IconButton, + Button, + CircularProgress, } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; import axios from "axios"; -import {geoServerBaseUrl} from "@/api/requests"; -import {green} from "@mui/material/colors"; +import { geoServerBaseUrl } from "@/api/requests"; +import { green } from "@mui/material/colors"; -const DOWNLOAD_URL = "/geoserver/vector/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=vector:download_v&outputFormat=FORMAT"; +const DOWNLOAD_URL = + "/geoserver/vector/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=vector:download_v&outputFormat=FORMAT"; interface DownloadPopupProps { - isOpen: boolean; - onClose: () => void; - cqlFilter: string; + isOpen: boolean; + onClose: () => void; + cqlFilter: string; } const DownloadPopup: React.FC = ({ - isOpen, - onClose, - cqlFilter, - }) => { - const [selectedFormat, setSelectedFormat] = useState("CSV"); - const [downloading, setLoading] = React.useState(false); - const handleFormatChange = (event: React.ChangeEvent) => { - setSelectedFormat(event.target.value); - }; + isOpen, + onClose, + cqlFilter, +}) => { + const [selectedFormat, setSelectedFormat] = useState("CSV"); + const [downloading, setLoading] = React.useState(false); + const handleFormatChange = (event: React.ChangeEvent) => { + setSelectedFormat(event.target.value); + }; - const handleDownload = () => { - setLoading(true) - axiosDownloadFile() - }; - const axiosDownloadFile = () => { - let url = geoServerBaseUrl + DOWNLOAD_URL.replace("FORMAT", selectedFormat); - console.log(`URL ${url}`) - if (cqlFilter) { - url += `&cql_filter=${cqlFilter}` - console.log(`URL ${url}`) - } + const handleDownload = () => { + setLoading(true); + axiosDownloadFile(); + }; + const axiosDownloadFile = () => { + let url = geoServerBaseUrl + DOWNLOAD_URL.replace("FORMAT", selectedFormat); + console.log(`URL ${url}`); + if (cqlFilter) { + url += `&cql_filter=${cqlFilter}`; + console.log(`URL ${url}`); + } - return axios.get( - url, - { - responseType: 'blob', - } - ) - .then(response => { - const href = window.URL.createObjectURL(response.data); + return axios + .get(url, { + responseType: "blob", + }) + .then((response) => { + const href = window.URL.createObjectURL(response.data); - const anchorElement = document.createElement('a'); + const anchorElement = document.createElement("a"); - anchorElement.href = href; - anchorElement.download = 'Occurrence'; + anchorElement.href = href; + anchorElement.download = "Occurrence"; - document.body.appendChild(anchorElement); - anchorElement.click(); + document.body.appendChild(anchorElement); + anchorElement.click(); - document.body.removeChild(anchorElement); - window.URL.revokeObjectURL(href); - setLoading(false) - }) - .catch(error => { - setLoading(false) - console.log('error: ', error); - }); - } + document.body.removeChild(anchorElement); + window.URL.revokeObjectURL(href); + setLoading(false); + }) + .catch((error) => { + setLoading(false); + console.log("error: ", error); + }); + }; + + return ( + + + + + + + + + + + - + Select data download format + + + + + } + label="CSV" + /> + {/*}*/} + {/* label="Excel"*/} + {/*/>*/} + } + label="GeoJSON" + /> + } + label="KML" + /> + } + label="SHP" + /> + {/* Add more options as needed */} + + + +
+ + - {downloading && ( - - )} - -
-
-
-
- ); + size="small" + style={{ fontSize: "0.7rem" }} + disabled={downloading} + onClick={handleDownload} + > + Download + + {downloading && ( + + )} + +
+ + + + ); }; export default DownloadPopup; diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index df202c1..8a7bb74 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -325,9 +325,9 @@ const Newmap = () => { > { Dudumapper - + */} { + + + setDownloadPopupOpen(!downloadPopupOpen)} + sx={{ + "&:hover": { + backgroundColor: alpha(theme.palette.primary.main, 0.1), + }, + backgroundColor: downloadPopupOpen + ? alpha(green[500], 0.1) + : "transparent", + }} + > + + + + + {downloadPopupOpen ? ( + + ) : ( + + )} + + + + setDownloadPopupOpen(false)} + cqlFilter={cqlFilter || ""} + /> + + + + + @@ -498,14 +564,7 @@ const Newmap = () => { {overlaysOpen ? : } - - setDownloadPopupOpen(true)}> - - - - - - + {renderLayerControls()} @@ -523,12 +582,6 @@ const Newmap = () => { }} /> - setDownloadPopupOpen(false)} - cqlFilter={cqlFilter || ""} - /> -