Skip to content

Commit

Permalink
Completed the marker upgrades
Browse files Browse the repository at this point in the history
Signed-off-by: Emil Balitzki <[email protected]>
Co-Authored-By: CelineMP <[email protected]>
  • Loading branch information
Corgam and CelineMP committed Jul 11, 2024
1 parent 84a3531 commit d80977f
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 90 deletions.
15 changes: 13 additions & 2 deletions backend/lib/BieMetadata/MetadataObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ public class AdditionalData
public int MarkersThreshold { get; set; } = 0;

/// <summary>
/// The display property is the property that should be shown in a popup.
/// A list of display properties that should be shown in a marker popup.
/// </summary>
public string DisplayProperty { get; set; } = string.Empty;
public List<DisplayProperty> DisplayProperty { get; set; } = new List<DisplayProperty>();

/// <summary>
/// Table data populated by the data pipeline. Contains the name and the size of the all .yaml files correlated to that specific dataset.
Expand All @@ -89,4 +89,15 @@ public class TableData

public BoundingBox? BoundingBox { get; set; }
}

/// <summary>
/// A list of display values to show for the markers on the map
/// </summary>
public class DisplayProperty
{
// The display name to show
public string displayName { get; set; } = string.Empty;
// The value to show
public string value { get; set; } = string.Empty;
}
}
8 changes: 4 additions & 4 deletions backend/metadata-database/init-db.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const datasets = [
LongDescription: `An empty, default map of Germany, with no data loaded. Useful for exploring the map.`,
MinZoomLevel: -1,
MarkersThreshold: -1,
DisplayProperty: "",
DisplayProperty: [],
Tables: [],
},
},
Expand All @@ -46,7 +46,7 @@ const datasets = [
LongDescription: `A map of EV charging stations displays the locations of electric vehicle charging points located in Germany, helping drivers plan routes and manage charging needs. It is essential for supporting the adoption and convenience of electric vehicles.`,
MinZoomLevel: 11,
MarkersThreshold: -1,
DisplayProperty: "name",
DisplayProperty: [{ displayName: "Operator", value: "text" }],
Tables: [],
},
},
Expand All @@ -64,7 +64,7 @@ const datasets = [
LongDescription: `House footprints refer to the outline or ground area covered by a house, typically measured from the exterior walls of the structure. This footprint includes all parts of the house that are in contact with the ground, and is important for planning and zoning purposes, calculating property taxes, and designing land use.`,
MinZoomLevel: 11,
MarkersThreshold: 17,
DisplayProperty: "",
DisplayProperty: [],
Tables: [],
},
},
Expand All @@ -82,7 +82,7 @@ const datasets = [
LongDescription: `The Actual Use map describes the use of the earth's surface in four main groups (settlement, traffic, vegetation and water bodies). The division of these main groups into almost 140 different types of use, such as residential areas, road traffic, agriculture or flowing water, enables detailed evaluations and analyses of the use of the earth's surface.`,
MinZoomLevel: 11,
MarkersThreshold: 17,
DisplayProperty: "",
DisplayProperty: [],
Tables: [],
},
},
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/DataView/DataView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ function DataView() {
currentMapCache.selectedCoordinates !== null &&
currentMapCache.loadedCoordinates !== currentMapCache.selectedCoordinates
) {
console.log("now selected coordinates different than loaded");
setIfNeedsReloading(true);
// Check if tab was switched
} else if (
Expand Down
139 changes: 68 additions & 71 deletions frontend/src/components/MapView/MapDatasetVisualizer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMap } from "react-leaflet";
import { useCallback, useContext, useEffect , useState, useRef} from "react";
import { FeatureCollection } from "geojson";
import { Fragment, useCallback, useContext, useEffect, useState } from "react";
import { Feature, FeatureCollection, GeoJsonProperties, Point } from "geojson";
import { MapContext } from "../../contexts/MapContext";
import { TabsContext } from "../../contexts/TabsContext";
import GeoDataFetcher from "./GeoDataFetcher";
Expand All @@ -11,15 +11,12 @@ import "proj4";
import { createRoot } from "react-dom/client";
import { flushSync } from "react-dom";
import { MapPin } from "@phosphor-icons/react";
import { Dataset } from "../../types/DatasetTypes";
import { Dataset, DisplayProperty } from "../../types/DatasetTypes";
import { MarkersTypes } from "../../types/MarkersTypes";
import { createDivIcon } from "../../utils/mergeIcons";
import { convertPolygonsToMarkers } from "../../utils/polgonsToMarkers";
import { Popup } from "react-leaflet/Popup";
import {
MarkerSelection
} from "../../types/MapSelectionTypes";

import { MarkerSelection } from "../../types/MapSelectionTypes";

interface MapDatasetVisualizerProps {
dataset: Dataset;
Expand All @@ -36,7 +33,7 @@ const renderToHtml = (Component: React.FC) => {
};

const divIcondefault: DivIcon = L.divIcon({
html: renderToHtml(() => <MapPin size={32} weight="duotone"/>),
html: renderToHtml(() => <MapPin size={32} weight="duotone" />),
className: "", // Optional: add a custom class name
iconSize: [34, 34],
iconAnchor: [17, 17], // Adjust the anchor point as needed
Expand All @@ -49,9 +46,14 @@ const MapDatasetVisualizer: React.FC<MapDatasetVisualizerProps> = ({
const { currentMapCache, setCurrentMapCache } = useContext(MapContext);

const { setCurrentTabsCache } = useContext(TabsContext);
const [popupData, setPopupData] = useState<any>(null);
const [popupData, setPopupData] = useState<Feature<
Point,
GeoJsonProperties
> | null>(null);
const [isPopupOpen, setIsPopupOpen] = useState(false);
const [latLngCoordinates, setLatLngCoordinates] = useState<LatLng>(new LatLng(51.505, -0.09));
const [latLngCoordinates, setLatLngCoordinates] = useState<LatLng>(
new LatLng(51.505, -0.09)
);

const updateDatasetData = useCallback(
(newData: FeatureCollection, bounds: LatLngBounds) => {
Expand Down Expand Up @@ -85,25 +87,6 @@ const MapDatasetVisualizer: React.FC<MapDatasetVisualizerProps> = ({
updateDatasetData
);


const handleMarkerClick = (feature: any, latlng: LatLng) => {
setPopupData(feature);
setIsPopupOpen(true);
setLatLngCoordinates(latlng);

const markerSelection = new MarkerSelection(
latlng,
'map marker',
true
);

setCurrentMapCache((prevCache) => ({
...prevCache,
selectedCoordinates: markerSelection,
}));

};

useEffect(() => {
// Check if data has been fetched
if (!geoData || !dataset.metaData) return;
Expand Down Expand Up @@ -137,22 +120,9 @@ const MapDatasetVisualizer: React.FC<MapDatasetVisualizerProps> = ({
icon: dataset.metaData
? createDivIcon(dataset.metaData.icon)
: divIcondefault,
}).on("click", () => {
handleMarkerClick(_feature, latlng)
// TODO change to PolygonSelection
const markerSelection = new MarkerSelection(
latlng,
"map polygon",
false
);
setCurrentMapCache((prevCache) => ({
...prevCache,
selectedCoordinates: markerSelection,
}));
});
return marker;
},

style: { fillOpacity: 0.1 },
});
const markerClusterGroup = L.markerClusterGroup();
Expand All @@ -167,27 +137,28 @@ const MapDatasetVisualizer: React.FC<MapDatasetVisualizerProps> = ({
// For Markers type datasets
} else {
const geojsonLayer = L.geoJson(geoData, {
pointToLayer: function (_feature, latlng) {
pointToLayer: function (feature, latlng) {
const marker = L.marker(latlng, {
icon: dataset.metaData
? createDivIcon(dataset.metaData.icon)
: divIcondefault,
}).on("click", () => {
handleMarkerClick(_feature, latlng)
setPopupData(feature);
setIsPopupOpen(true);
setLatLngCoordinates(latlng);
// Select a marker on the map
const markerSelection = new MarkerSelection(
latlng,
"map marker",
"Custom Marker",
true
);
setCurrentMapCache((prevCache) => ({
...prevCache,
selectedCoordinates: markerSelection,
}));
);
setCurrentMapCache((prevCache) => ({
...prevCache,
selectedCoordinates: markerSelection,
}));
});
return marker;
},

style: { fillOpacity: 0.1 },
});
const markerClusterGroup = L.markerClusterGroup();
Expand All @@ -199,35 +170,61 @@ const MapDatasetVisualizer: React.FC<MapDatasetVisualizerProps> = ({
map.removeLayer(markerClusterGroup);
};
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataset, currentMapCache.zoom, map, geoData]);

/**
* Creates a list of the properties for the popup data
* @param popupData the popup data object
* @param displayProperties the list of properties to display with displayName and value fields
* @returns a list of strings
*/
const listProperties = (popupData: GeoJsonProperties): DisplayProperty[] => {
const listOfProperties: DisplayProperty[] = [];
if (popupData && dataset.metaData) {
for (const property in popupData.properties) {
if (
Object.prototype.hasOwnProperty.call(popupData.properties, property)
) {
// Check if the property is in the displayProperties list
const displayProperty = dataset.metaData.displayProperty.find(
(dp) => dp.value === property
);
// If it is, format and push to the list
if (displayProperty) {
listOfProperties.push({
displayName: displayProperty.displayName,
value: String(popupData.properties[property]),
});
}
}
}
}
return listOfProperties;
};

// PopUp marker
// dataset.metaData.displayProperty (set in backend/metadata-database/init-db.js) list of properties that should be shown
// popupData.properties | list of objects { {displayName: "displayName1", propertyName: "propertyName1"}, {…}, …}
return (
<>
{isPopupOpen && popupData && (

<Popup position={latLngCoordinates} offset={[0, -10]}>
{popupData.properties && popupData.properties.length > 0 ? (
popupData.properties
.filter((property: any) => dataset.metaData?.displayProperty.includes(property.propertyName))
.map((property: any, index: number) => (
<div key={index}>
<strong>{property.propertyName}:</strong> <span>{property.displayName}</span>
</div>
))
) : (
<p>No data available</p>
<Popup position={latLngCoordinates} offset={[0, -25]}>
{popupData.properties ? (
<Fragment>
{listProperties(popupData).map((displayProperty) => {
return (
<div key={displayProperty.displayName}>
<b>{displayProperty.displayName}: </b>
<span>{displayProperty.value}</span>
</div>
);
})}
</Fragment>
) : (
<p>No data available</p>
)}
</Popup>
)}
</Popup>

)}
</>
);

};

export default MapDatasetVisualizer;

3 changes: 3 additions & 0 deletions frontend/src/components/MapView/MapEventsHandler.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.selection-icon {
z-index: -10 !important;
}
25 changes: 15 additions & 10 deletions frontend/src/components/MapView/MapEventsHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Fragment, useContext } from "react";
import React, { useContext } from "react";
import { Marker } from "react-leaflet";
import { useMapEvents } from "react-leaflet/hooks";
import { MapContext } from "../../contexts/MapContext";
Expand All @@ -7,6 +7,7 @@ import { MapPin } from "@phosphor-icons/react";
import { createRoot } from "react-dom/client";
import { flushSync } from "react-dom";
import { MarkerSelection } from "../../types/MapSelectionTypes";
import "./MapEventsHandler.css";

// Utility function to render a React component to HTML string
const renderToHtml = (Component: React.FC) => {
Expand All @@ -19,8 +20,10 @@ const renderToHtml = (Component: React.FC) => {
};

const divIconMarker: DivIcon = L.divIcon({
html: renderToHtml(() => <MapPin size={36} color="#ff0000" weight="fill" />),
className: "", // Optional: add a custom class name
html: renderToHtml(() => (
<MapPin size={36} color="#ff0000" weight="fill" style={{ zIndex: "-10" }} />
)),
className: "selection-icon", // Optional: add a custom class name
iconSize: [36, 36],
iconAnchor: [18, 36], // Adjust the anchor point as needed
});
Expand Down Expand Up @@ -53,13 +56,15 @@ const MapEventsHandler: React.FC = () => {
},
});

return currentMapCache.selectedCoordinates instanceof MarkerSelection ? (
<Marker
position={currentMapCache.selectedCoordinates.marker}
icon={divIconMarker}
/>
) : (
<Fragment />
return (
currentMapCache.selectedCoordinates instanceof MarkerSelection && (
<div className="selection-marker">
<Marker
position={currentMapCache.selectedCoordinates.marker}
icon={divIconMarker}
/>
</div>
)
);
};

Expand Down
7 changes: 6 additions & 1 deletion frontend/src/types/DatasetTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ export interface DatasetMetaData {
longDescription: string;
minZoomLevel: number;
markersThreshold: number;
displayProperty: string;
displayProperty: DisplayProperty[];
tables: Table[];
}

export interface DisplayProperty {
displayName: string;
value: string;
}

export interface Table {
name: string;
numberOfLines: number;
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/utils/mergeIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export const mergeIcons = (
return combinedHtml;
};

/**
* The marker on the map
*/
export const pinSvg =
'<svg width="32" height="32" fill="#000000" viewBox="0 0 256 256" version="1.1" id="svg1" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <defs id="defs1" /> <path d="m 128,16 a 88.1,88.1 0 0 0 -88,88 c 0,75.3 80,132.17 83.41,134.55 a 8,8 0 0 0 9.18,0 C 136,236.17 216,179.3 216,104 A 88.1,88.1 0 0 0 128,16 Z m 0,56 a 32,32 0 1 1 -32,32 32,32 0 0 1 32,-32 z" id="path1" /> <ellipse style="fill:#ffffff;stroke:#000000;stroke-width:0;stroke-dasharray:none;stroke-opacity:1" id="path12" cx="128.39645" cy="104.18782" rx="81.751793" ry="81.967773" /></svg>';

Expand All @@ -53,6 +56,6 @@ export const createDivIcon = (iconSvgString: string) => {
html: combinedSvg,
className: "", // Optional: add a custom class name
iconSize: [40, 40],
iconAnchor: [20, 20], // Adjust the anchor point as needed
iconAnchor: [20, 40], // Adjust the anchor point as needed
});
};

0 comments on commit d80977f

Please sign in to comment.