mirror of
https://github.com/Queer-Lexikon/regenbogenkarte
synced 2024-11-22 20:23:13 +00:00
139 lines
4.2 KiB
TypeScript
139 lines
4.2 KiB
TypeScript
|
import * as L from "leaflet";
|
||
|
import "leaflet.markercluster";
|
||
|
|
||
|
import "leaflet/dist/leaflet.css";
|
||
|
import data from "../data/orgas.json";
|
||
|
|
||
|
import defaultImage from "../assets/default.svg";
|
||
|
import limitedImage from "../assets/limited.svg";
|
||
|
import supportImage from "../assets/support.svg";
|
||
|
import { DEFAULT_ZOOM_LEVEL, ICON_SIZE, MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from "./config";
|
||
|
|
||
|
type Organisation = {
|
||
|
country: string;
|
||
|
state: string;
|
||
|
name: string;
|
||
|
email?: string;
|
||
|
website?: string;
|
||
|
phone?: string;
|
||
|
location: {
|
||
|
address?: string;
|
||
|
lon: number;
|
||
|
lat: number;
|
||
|
approx?: boolean;
|
||
|
};
|
||
|
activities?: string;
|
||
|
identities?: string;
|
||
|
age_restriction?: string;
|
||
|
};
|
||
|
|
||
|
let map: L.Map;
|
||
|
export function initializeMap() {
|
||
|
map = L.map("map").setView([51.351, 10.454], MIN_ZOOM_LEVEL); // focus on germany
|
||
|
map.locate({ setView: true, maxZoom: DEFAULT_ZOOM_LEVEL });
|
||
|
|
||
|
L.tileLayer("https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png", {
|
||
|
maxZoom: MAX_ZOOM_LEVEL,
|
||
|
minZoom: MIN_ZOOM_LEVEL,
|
||
|
attribution: "© OpenStreetMap",
|
||
|
}).addTo(map);
|
||
|
|
||
|
var markers = L.markerClusterGroup({
|
||
|
// Displaying the areas of each cluster is not intended. It might confuse our users
|
||
|
// and provides no benefit.
|
||
|
showCoverageOnHover: false,
|
||
|
|
||
|
// The default lines are barely visible, especially on the German map. Therefore, thicker
|
||
|
// and more visible lines are placed.
|
||
|
spiderLegPolylineOptions: {
|
||
|
color: "black",
|
||
|
opacity: 0.8,
|
||
|
weight: 2,
|
||
|
},
|
||
|
|
||
|
// Our icons are too large for the default multiplier, this increases the distance between the elements.
|
||
|
// Maybe we should even increase it further to improve the usability on mobile devices.
|
||
|
spiderfyDistanceMultiplier: 2,
|
||
|
|
||
|
// Reduce the cluster radius from 80px to 20px. This creates more and smaller clusters.
|
||
|
maxClusterRadius: 20,
|
||
|
|
||
|
// Let's use the default icon for our clusters and place a white circle inside of it.
|
||
|
iconCreateFunction: (cluster) => {
|
||
|
return new L.DivIcon({
|
||
|
html: `<div>
|
||
|
<img src="${defaultImage}" alt=""/>
|
||
|
<span class="rounded-full bg-white absolute top-[10px] bottom-[13px] inset-x-3 flex justify-center items-center font-semibold text-sm">${cluster.getChildCount()}</span></div>`,
|
||
|
className: "", // empty className, so leaflet does not set any defaults.
|
||
|
iconSize: ICON_SIZE,
|
||
|
});
|
||
|
},
|
||
|
});
|
||
|
data.forEach((row) => {
|
||
|
const marker = createMarker(row);
|
||
|
if (marker) markers.addLayer(marker);
|
||
|
});
|
||
|
map.addLayer(markers);
|
||
|
}
|
||
|
|
||
|
const defaultIcon = L.icon({
|
||
|
iconUrl: defaultImage,
|
||
|
iconSize: ICON_SIZE,
|
||
|
});
|
||
|
const limitedIcon = L.icon({
|
||
|
iconUrl: limitedImage,
|
||
|
iconSize: ICON_SIZE,
|
||
|
});
|
||
|
const supportIcon = L.icon({
|
||
|
iconUrl: supportImage,
|
||
|
iconSize: ICON_SIZE,
|
||
|
});
|
||
|
|
||
|
const createMarker = (e: Organisation): L.Marker | undefined => {
|
||
|
const marker = L.marker([e.location.lon, e.location.lat], { alt: e.name }).bindPopup(
|
||
|
buildContent(e),
|
||
|
{ className: "map-popup" },
|
||
|
);
|
||
|
|
||
|
if (e.identities && e.identities !== "") {
|
||
|
marker.setIcon(limitedIcon);
|
||
|
} else if (e.activities && e.activities.toLowerCase().includes("beratung")) {
|
||
|
marker.setIcon(supportIcon);
|
||
|
} else {
|
||
|
marker.setIcon(defaultIcon);
|
||
|
}
|
||
|
|
||
|
return marker;
|
||
|
};
|
||
|
|
||
|
const buildContent = (o: Organisation): string => {
|
||
|
let result = `
|
||
|
<h3 class="font-semibold text-base">${o.name}</h3>
|
||
|
<address class="inline">${o.location.address ?? "auf Nachfrage"}</address>
|
||
|
<ul class="list-disc my-4 pl-2 list-inside">`;
|
||
|
|
||
|
if (o.website) result += `<li><a href="${o.website}">Zur Webseite</a></li>`;
|
||
|
if (o.email) result += `<li><a href="mailto:${o.email}">E-Mail</a></li>`;
|
||
|
if (o.phone) result += `<li>Telefon: <a href="tel:${o.phone}">${o.phone}</a></li>`;
|
||
|
result += `</ul>`;
|
||
|
|
||
|
if (o.activities && o.activities !== "")
|
||
|
result += `<h4 class="font-semibold">Aktivitäten</h4>
|
||
|
<p>${o.activities}</p>`;
|
||
|
|
||
|
if (o.identities && o.identities !== "")
|
||
|
result += `<h4 class="font-semibold">Identitäten</h4>
|
||
|
<p>${o.identities}</p>`;
|
||
|
|
||
|
if (o.age_restriction && o.age_restriction !== "")
|
||
|
result += `<h4 class="font-semibold">Altersbeschränkung</h4><p>vorhanden: ${o.age_restriction}</p>`;
|
||
|
|
||
|
if (o.location.approx) {
|
||
|
result += `<p class="text-xs !mt-2">Bei den Daten handelt es sich um eine ungefähre Ortsangabe. Die genaue Adresse erfährst du auf Nachfrage.</p>`;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
export const getMap = (): L.Map => map;
|