Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 38 additions & 10 deletions components/MyLeafletMap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,19 @@ function findValueByKey(obj: unknown, key: string): string | number | undefined
return undefined;

const record = obj as Record<string, unknown>;

if (key in record) {
const value = record[key];
if (typeof value === 'string' || typeof value === 'number')
return value;
const targetKey = key.toLowerCase();

// Case-insensitive key lookup
for (const k of Object.keys(record)) {
if (k.toLowerCase() === targetKey) {
const value = record[k];
if (typeof value === 'string' || typeof value === 'number') {
return value;
}
}
}

// Recursively search nested objects
for (const value of Object.values(record)) {
const result = findValueByKey(value, key);
if (result !== undefined)
Expand Down Expand Up @@ -73,10 +79,13 @@ function generateLabels(data: GeoJSON.FeatureCollection): Map<string, string> {

if (legendDisplayOption[0] === 'default') {
uniqueValues.forEach((value) => {
if (value === undefined) {
if (value === undefined)
return;
}
const match = legendDetail.find((item: LegendDetails) => item.label === value);

const match = legendDetail.find(
(item: LegendDetails) => item.label.toLowerCase() === String(value).toLowerCase(),
);

if (match?.color) {
colorMap.set(value, match.color);
}
Expand Down Expand Up @@ -177,7 +186,7 @@ function generateLabels(data: GeoJSON.FeatureCollection): Map<string, string> {
});
}

if (leafletMap) {
if (leafletMap && legend.onAdd) {
legend.addTo(leafletMap);
legendControl = legend;
}
Expand Down Expand Up @@ -209,7 +218,22 @@ function renderMarkers(data: GeoJSON.FeatureCollection | undefined) {
data.features.forEach((feature) => {
const legendOption = feature.properties?.options?.legend_option;
const labelOption = feature.properties?.options?.label_option;
let key = labelOption ? feature.properties?.[labelOption] : undefined;
let key: string = 'default';

if (labelOption && feature.properties) {
const normalizedKey = Object.keys(feature.properties).find(
k => k.toLowerCase() === labelOption.toLowerCase(),
);
if (normalizedKey) {
const value = feature.properties[normalizedKey];
if (typeof value === 'string') {
key = value.trim();
}
else {
key = value;
}
}
}

if (legendOption === 'colorVarient') {
key = feature.properties?.__binLabel;
Expand Down Expand Up @@ -264,6 +288,10 @@ function renderMarkers(data: GeoJSON.FeatureCollection | undefined) {
geoJsonLayer.addTo(leafletMap as L.Map);
geoJsonLayers.push(geoJsonLayer);
});
const bounds = new L.LatLngBounds(geoJsonLayers.map(layer => [layer.getBounds().getNorthEast(), layer.getBounds().getSouthWest()]).flat());
if (bounds.isValid()) {
leafletMap?.fitBounds(bounds, { padding: [50, 50] });
}
}

function resetSelectedMarker() {
Expand Down
34 changes: 20 additions & 14 deletions components/Slider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
>
<div class="relative w-full h-2 rounded overflow-hidden">
<div
v-for="group in props.dateGroup" :key="group.year" class="absolute h-full" :style="{
v-for="group in groupedDates" :key="group.year" class="absolute h-full" :style="{
left: `${group.offset}%`,
width: `${group.width}%`,
backgroundColor: group.color,
Expand All @@ -14,25 +14,25 @@

<div class="-mt-[17px]">
<input
:value="props.selectedIndex" type="range" :min="0" :max="props.dateOptions.length - 1"
class="custom-slider w-full relative z-10" @input="emit('update:selectedIndex', +$event.target.value)"
v-model.number="modelValue" type="range" :min="0" :max="dateOptions.length - 1"
class="custom-slider w-full relative z-10"
>
</div>

<div class="relative w-full h-5 mt-2">
<span
v-for="group in props.dateGroup" :key="group.year" class="absolute text-sm whitespace-nowrap font-medium" :style="{
v-for="group in groupedDates" :key="group.year" class="absolute text-sm text-white whitespace-nowrap font-medium" :style="{
left: `${parseFloat(group.offset) + parseFloat(group.width) / 2}%`,
transform: 'translateX(-50%)',
fontSize: props.isSmallScreen ? '0.50rem' : '1rem',
fontSize: isSmallScreen ? '0.50rem' : '1rem',
}"
>
{{ group.year }}
</span>
</div>

<div class="mt-2 text-center font-semibold">
{{ t('selectedDate') }}: {{ props.selectedDate }}
<div class="mt-2 text-center text-white font-semibold">
{{ t('selectedDate') }}: {{ selectedDate }}
</div>
</div>
</template>
Expand All @@ -41,16 +41,22 @@
import { useI18n } from 'vue-i18n';

const props = defineProps<{
dateGroup: DateGroup[]
dateOptions: DateOptions[]
selectedIndex: number
selectedDate: string
dateOptions: string[]
isSmallScreen: boolean
}>();

const emit = defineEmits<{
(e: 'update:selectedIndex', value: number): void
}>();
const modelValue = defineModel<number>({ required: true });

const selectedDate = computed(() => {
if (props.dateOptions && props.dateOptions.length > 0) {
return props.dateOptions[modelValue.value];
}
return '';
});

const groupedDates = computed(() => {
return getDatesGroups(props.dateOptions);
});

const { t } = useI18n();
</script>
Expand Down
9 changes: 3 additions & 6 deletions composables/dataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ export interface DateGroup {
color: string
}

export interface DateOptions {
[key: number]: string
}

export interface DataEntry {
[key: string]: string | number
}
Expand All @@ -28,8 +24,9 @@ export interface Options {
legend_option: 'default' | 'colorVariant'
type: string
value_group: string
coordinate_field_x?: string
coordinate_field_y?: string
crs?: string | Record<string, string>
latitude_field?: string | Record<string, string>
longitude_field?: string | Record<string, string>
display_option: 'popup' | 'line chart'
popup_name?: string
popup_details?: { label: string, prop: string | string[] }[]
Expand Down
48 changes: 21 additions & 27 deletions composables/useSliderDates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,30 @@ function isFeatureCollectionWithDate(item: GeoJSON.FeatureCollection): item is G
return (item as GeoJSON.FeatureCollection & { date: string }).date !== undefined;
}

export function getDatesGroups(fetchedData: GeoJSON.FeatureCollection[]) {
const groupedDates = computed(() => {
const yearGroups = new Map<string, string[]>();
fetchedData
.filter(isFeatureCollectionWithDate)
.forEach((item) => {
const year = item.date.slice(0, 4);
if (!yearGroups.has(year)) {
yearGroups.set(year, []);
}
yearGroups.get(year)!.push(item.date);
});
export function getDatesGroups(dateOptions: string[]) {
const yearGroups = new Map<string, string[]>();
dateOptions.forEach((date) => {
const year = date.slice(0, 4);
if (!yearGroups.has(year)) {
yearGroups.set(year, []);
}
yearGroups.get(year)!.push(date);
});

const total = Array.from(yearGroups.values()).reduce((acc, arr) => acc + arr.length, 0);
let offset = 0;
const total = Array.from(yearGroups.values()).reduce((acc, arr) => acc + arr.length, 0);
let offset = 0;

return Array.from(yearGroups.entries()).map(([year, dates], i) => {
const width = (dates.length / total) * 100;
const group = {
year,
width: width.toFixed(2),
offset: offset.toFixed(2),
color: yearColors[i % yearColors.length],
};
offset += width;
return group;
});
return Array.from(yearGroups.entries()).map(([year, dates], i) => {
const width = (dates.length / total) * 100;
const group = {
year,
width: width.toFixed(2),
offset: offset.toFixed(2),
color: yearColors[i % yearColors.length],
};
offset += width;
return group;
});

return groupedDates.value;
}

export function getDateOptions(fetchedData: GeoJSON.FeatureCollection[]): string[] {
Expand Down
4 changes: 2 additions & 2 deletions data/bathingWaterInputLayer.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
{ "label": "SAISON", "prop": ["SAISONBEGINN", "SAISONENDE", "GESCHLOSSEN"] },
{ "label": "INFRASTRUKTUR", "prop": "INFRASTRUKTUR" }
],
"coordinate_field_x": "GEOGR_BREITE",
"coordinate_field_y": "GEOGR_LAENGE"
"latitude_field": "GEOGR_BREITE",
"longitude_field": "GEOGR_LAENGE"
}
}
3 changes: 2 additions & 1 deletion data/mapDisplayOptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"options": [
{ "name": "bathing", "title": "Badegewässer" },
{ "name": "lakes", "title": "Pegelstände" },
{ "name": "trees", "title": "Bäume Norderstedt" }
{ "name": "trees", "title": "Bäume Norderstedt" },
{ "name": "windpower", "title": "Windkraftanlagen" }
]
}
59 changes: 59 additions & 0 deletions data/windpowerInputLayer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"datasets": [
{
"host": "opendata.schleswig-holstein.de",
"id": "windkraftanlagen",
"title": "Windkraftanlagen"
}
],
"mappings": [],
"options":
{
"label_option": "STATUS",
"legend_option": "default",
"legend_details": [
{ "label": "in Betrieb", "color": "#10B981" },
{ "label": "vor Inbetriebnahme", "color": "#0D9488" },
{ "label": "im Gen.Verf.", "color": "#3B82F6" }
],
"type": "series",
"value_group": "",
"display_option": "popup",
"popup_name": "AKTENZEICHEN",
"popup_details": [
{ "label": "Kreis", "prop": "Kreis" },
{ "label": "Gemeinde", "prop": "Gemeinde" },
{ "label": "Typ", "prop": "Typ" },
{ "label": "Hersteller", "prop": "Hersteller" },
{ "label": "Nabenhöhe", "prop": "Nabenhöhe" },
{ "label": "Rotordurchmesser", "prop": "Rotordurchmesser" },
{ "label": "Genehmigungsdatum", "prop": "Genehmigungsdatum" },
{ "label": "Inbetriebnahme", "prop": "Inbetriebnahme" },
{ "label": "Datendatum", "prop": "Datendatum" }
],
"crs": {
"windkraftanlagen": "EPSG:25832",
"windkraftanlagen/65f105e1-f7c0-48a1-9697-d77cef43d25c": "EPSG:4647",
"windkraftanlagen/12fb2027-d2d3-42c9-8774-34a70f584c0f": "EPSG:4647",
"windkraftanlagen/e1170052-286b-460e-9155-28b0bc44ae18": "EPSG:4647",
"windkraftanlagen/2c7d6758-e267-41ca-a68e-9dc9d5664ae8": "EPSG:4647",
"windkraftanlagen/7f93b5e0-d7be-48cb-9236-cffcfa12693a": "EPSG:4647",
"windkraftanlagen/f5287f83-47d1-459d-9440-00fd3154e108": "EPSG:4647",
"windkraftanlagen/9b4b5428-52d4-4676-a450-9ffb98b4670f": "EPSG:4647",
"windkraftanlagen/8623a709-4811-4c7f-b5d7-21203d24f11b": "EPSG:4647",
"windkraftanlagen/5fb10b92-97eb-4b2a-83d8-17cd17139ca5": "EPSG:4647"
},
"latitude_field": {
"windkraftanlagen": "NORDWERT",
"windkraftanlagen/9b4b5428-52d4-4676-a450-9ffb98b4670f": "Nordwert",
"windkraftanlagen/8623a709-4811-4c7f-b5d7-21203d24f11b": "Nordwert",
"windkraftanlagen/5fb10b92-97eb-4b2a-83d8-17cd17139ca5": "AN_HW"
},
"longitude_field": {
"windkraftanlagen": "OSTWERT",
"windkraftanlagen/9b4b5428-52d4-4676-a450-9ffb98b4670f": "Ostwert",
"windkraftanlagen/8623a709-4811-4c7f-b5d7-21203d24f11b": "Ostwert",
"windkraftanlagen/5fb10b92-97eb-4b2a-83d8-17cd17139ca5": "AN_RW"
}
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"leaflet": "^1.9.4",
"leaflet-defaulticon-compatibility": "^0.1.2",
"nuxt": "3.19.0",
"proj4": "^2.15.0",
"proj4js-definitions": "^0.1.0",
"proj4leaflet": "^1.0.2",
"shpjs": "^6.1.0",
"tailwindcss": "4.1.5",
"vue-chartjs": "^5.3.2",
Expand Down
12 changes: 4 additions & 8 deletions pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,8 @@

<MyLeafletMap ref="leafletMapRef" class="flex-grow" :fetched-data="fetchedData" @marker-click="selectedItem = $event" />
<Slider
v-if="isDataSeries && dateOptions" :date-group="dateGroup" :date-options="dateOptions"
:selected-index="selectedIndex" :selected-date="selectedDate" :is-small-screen="isSmallScreen"
class="flex-0"
@update:selected-index="selectedIndex = $event"
v-if="isDataSeries && dateOptions" v-model="selectedIndex"
:date-options="dateOptions" :is-small-screen="isSmallScreen"
/>
<PopupInfo
v-if="selectedItem?.properties?.options?.display_option === 'popup'" :selected-item="selectedItem"
Expand Down Expand Up @@ -121,7 +119,7 @@ import LineChart from '~/components/LineChart.vue';
import MyLeafletMap from '~/components/MyLeafletMap.vue';
import PopupInfo from '~/components/PopupInfo.vue';
import Slider from '~/components/Slider.vue';
import { getDateOptions, getDatesGroups } from '~/composables/useSliderDates';
import { getDateOptions } from '~/composables/useSliderDates';
import featureOptionsJson from '~/data/mapDisplayOptions.json';

const featureOptions = featureOptionsJson.options;
Expand Down Expand Up @@ -159,8 +157,7 @@ const feature = computed<string | null>(() => {
const selectedIndex = ref(0);
const selectedItem = ref<GeoJSON.Feature | null>(null);
const selectedDate = ref();
let dateOptions: DateOptions[];
let dateGroup: DateGroup[];
let dateOptions: string[];
const loading = ref(false);
const isDataSeries = ref(false);

Expand Down Expand Up @@ -211,7 +208,6 @@ watch(feature, async (newval) => {
if (isDataSeries.value) {
seriesData.value = featureCollections;
dateOptions = getDateOptions(seriesData.value);
dateGroup = getDatesGroups(seriesData.value);
selectedIndex.value = dateOptions.length - 1;
selectedDate.value = dateOptions[selectedIndex.value];
}
Expand Down
Loading