/* Quick City IFM — Berlin coverage map: real Bezirke geodata (same pattern as the LNS Germany map) */ let _berlinDataCache = null; async function loadBerlinData() { if (_berlinDataCache) return _berlinDataCache; try { const resp = await fetch('https://cdn.jsdelivr.net/gh/funkeinteraktiv/Berlin-Geodaten@master/berlin_bezirke.geojson'); const geo = await resp.json(); _berlinDataCache = geo; } catch (e) { _berlinDataCache = null; } return _berlinDataCache; } const QC_IFM_BERLIN_VIEWBOX = { w: 100, h: 76 }; // fallback until real data loads // Real approximate coordinates [lng, lat] per named cluster area. const QC_IFM_BERLIN_CLUSTERS = [ { id: 'hsl', name: 'Hohenschönhausen / Lichtenberg / Weißensee', count: 45, lng: 13.49, lat: 52.545 }, { id: 'ppb', name: 'Pankow / Prenzlauer Berg / südl. Weißensee', count: 37.5, lng: 13.41, lat: 52.555 }, { id: 'cwg', name: 'Charlottenburg / Wilmersdorf / Grunewald', count: 37.5, lng: 13.30, lat: 52.50 }, { id: 'fkm', name: 'Friedrichshain / Kreuzberg / östl. Mitte', count: 32.5, lng: 13.42, lat: 52.51 }, { id: 'wmm', name: 'Wedding / Moabit / nördl. Mitte', count: 32.5, lng: 13.35, lat: 52.535 }, { id: 'trw', name: 'Tegel / Reinickendorf / Wittenau', count: 32.5, lng: 13.30, lat: 52.58 }, { id: 'mha', name: 'Marzahn / Hellersdorf / Ahrensfelde', count: 32.5, lng: 13.585,lat: 52.55 }, { id: 'slz', name: 'Steglitz / Lichterfelde / Zehlendorf', count: 32.5, lng: 13.30, lat: 52.44 }, { id: 'ssf', name: 'Spandau / Staaken / Falkenhagener Feld', count: 27.5, lng: 13.18, lat: 52.545 }, { id: 'nbt', name: 'Neukölln / Britz / nördl. Treptow', count: 27.5, lng: 13.45, lat: 52.475 }, { id: 'tsf', name: 'Tiergarten / Schöneberg / Friedenau', count: 27.5, lng: 13.34, lat: 52.487 }, { id: 'bkp', name: 'Buch / Karow / Panketal', count: 22.5, lng: 13.50, lat: 52.62 }, { id: 'fhh', name: 'Frohnau / Hermsdorf / Hohen Neuendorf', count: 22.5, lng: 13.29, lat: 52.64 }, { id: 'tmm', name: 'Tempelhof / Mariendorf / Marienfelde', count: 22.5, lng: 13.38, lat: 52.44 }, { id: 'kas', name: 'Köpenick / Adlershof / südöstl. Berlin', count: 20, lng: 13.57, lat: 52.44 }, { id: 'lbs', name: 'Lichtenrade / Buckow / Schönefeld', count: 17.5, lng: 13.45, lat: 52.40 }, { id: 'wkg', name: 'Wannsee / Kladow / Gatow', count: 12.5, lng: 13.15, lat: 52.44 }, { id: 'fal', name: 'Falkensee (Brandenburg)', count: 5, lng: 13.08, lat: 52.56, outside: true } ]; const QC_IFM_HQ = { name: 'Zentrale — Blankenburger Str. 18, 13089 Berlin', lng: 13.459, lat: 52.586 }; function IfmBerlinMap({ rightShift } = {}) { const { t } = window.useT ? window.useT() : { t: (k, f) => f }; const [data, setData] = React.useState(_berlinDataCache); const [hoverId, setHoverId] = React.useState(null); const [hoverCellIdx, setHoverCellIdx] = React.useState(null); const [hoverHQ, setHoverHQ] = React.useState(false); const clusters = QC_IFM_BERLIN_CLUSTERS; const hovered = hoverId ? clusters.find(c => c.id === hoverId) : null; const totalSites = Math.round(clusters.reduce((s, c) => s + c.count, 0)); React.useEffect(() => { if (!data) { let cancelled = false; loadBerlinData().then(d => { if (!cancelled) setData(d); }); return () => { cancelled = true; }; } }, []); const projected = React.useMemo(() => { if (typeof window === 'undefined' || !window.d3 || !data) return null; const padTop = 4, padRight = rightShift ? 12 : 18, padBottom = 6, padLeft = rightShift ? 24 : 18, w = 100; // Aspect ratio comes ONLY from Berlin's own bounds (stable, known-good data) — the live // Falkensee fetch is drawn on top afterward and never allowed to affect the box shape/scale. const [[minLng, minLat], [maxLng, maxLat]] = window.d3.geoBounds(data); const midLat = (minLat + maxLat) / 2; const dx = (maxLng - minLng) * Math.cos(midLat * Math.PI / 180); const dy = (maxLat - minLat); const innerW = w - padLeft - padRight; const innerH = innerW * (dy / dx); const h = Math.round(innerH + padTop + padBottom); const proj = window.d3.geoMercator().fitExtent([[padLeft, padTop], [w - padRight, innerH + padTop]], data); const path = window.d3.geoPath(proj); const districtPaths = data.features.map(f => ({ name: f.properties && f.properties.name, d: path(f) })); const points = clusters.map(c => { const r = proj([c.lng, c.lat]) || [0, 0]; return { ...c, x: r[0], y: r[1] }; }); const hq = proj([QC_IFM_HQ.lng, QC_IFM_HQ.lat]) || [0, 0]; // Falkensee has no reliable live geodata source (fetch has twice produced degenerate/huge // geometry and broken the layout) — draw a small fixed decorative blob at its marker instead. const falMarker = clusters.find(c => c.id === 'fal'); const markerProj = proj([falMarker.lng, falMarker.lat]); const falX = Math.max(padLeft * 0.5, 6); const falY = markerProj[1]; const falkenseeBlob = { x: falX, y: falY, r: 5 }; return { districtPaths, points, hq: { x: hq[0], y: hq[1] }, falkenseeBlob, w, h, padLeft }; }, [data]); const hoveredBezirk = (!hovered && hoverCellIdx !== null && projected) ? projected.districtPaths[hoverCellIdx] : null; const falMarkerFixed = projected && projected.falkenseeBlob ? { x: projected.falkenseeBlob.x, y: projected.falkenseeBlob.y } : null; return (
{projected ? ( <> {projected.falkenseeBlob && ( )} {projected.districtPaths.map((c, i) => ( setHoverCellIdx(i)} onMouseLeave={() => setHoverCellIdx(null)} /> ))} {projected.points.map(c => { const isActive = hoverId === c.id; const dimmed = hoverId && !isActive; const cx = (c.id === 'fal' && falMarkerFixed) ? falMarkerFixed.x : c.x; const cy = (c.id === 'fal' && falMarkerFixed) ? falMarkerFixed.y : c.y; const n = Math.max(1, Math.round(c.count / 8)); const pins = Array.from({length: n}, (_, i) => { const seed = (cx * 7 + cy * 13 + i * 31) % 1; const seed2 = (cx * 17 + cy * 3 + i * 53) % 1; const ang = seed * Math.PI * 2; const dist = 2.4 + seed2 * 6.5; return { x: cx + Math.cos(ang) * dist, y: cy + Math.sin(ang) * dist * 0.7 }; }); const s = 0.2 * (isActive ? 1.2 : 1); return ( setHoverId(c.id)} onMouseLeave={() => setHoverId(null)}> {pins.map((p, i) => ( ))} ); })} {projected.hq && ( setHoverHQ(true)} onMouseLeave={() => setHoverHQ(false)}> )} ) : ( Karte lädt … )}
{hoverHQ ? ( <>
{t('ifmmap.hq', 'Zentrale')}
{QC_IFM_HQ.name}
) : hovered ? ( <>
{Math.round(hovered.count)} {t('ifmmap.objects', 'Objekte / Standorte')}
{hovered.name}
) : hoveredBezirk ? ( <>
{t('ifmmap.bezirk', 'Bezirk')}
{hoveredBezirk.name}
) : (
{totalSites}+{t('ifmmap.sites', 'Objekte')}
{clusters.length}{t('ifmmap.regions', 'Gebiete')}
{t('ifmmap.hint', 'Bewegen Sie den Mauszeiger über einen Punkt')}
)}
); } Object.assign(window, { IfmBerlinMap, QC_IFM_BERLIN_CLUSTERS });