import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { AttributionControl, MapContainer, ZoomControl, useMapEvent, Pane, useMap } from 'react-leaflet'; import 'leaflet/dist/leaflet.css'; import './map.css'; import { apiGet } from '../apiHelpers'; import { HelpIcon } from '../components/icons'; import { categoryMapsConfig } from '../config/category-maps-config'; import { Category } from '../config/categories-config'; import { initialMapViewport, mapBackgroundColor, MapTheme, BoroughEnablementState, ParcelEnablementState, FloodEnablementState, ConservationAreasEnablementState, HistoricDataEnablementState } from '../config/map-config'; import { Building } from '../models/building'; import { CityBaseMapLayer } from './layers/city-base-map-layer'; import { CityBoundaryLayer } from './layers/city-boundary-layer'; import { BoroughBoundaryLayer } from './layers/borough-boundary-layer'; import { ParcelBoundaryLayer } from './layers/parcel-boundary-layer'; import { HistoricDataLayer } from './layers/historic-data-layer'; import { FloodBoundaryLayer } from './layers/flood-boundary-layer'; import { ConservationAreaBoundaryLayer } from './layers/conservation-boundary-layer'; import { BuildingBaseLayer } from './layers/building-base-layer'; import { BuildingDataLayer } from './layers/building-data-layer'; import { BuildingNumbersLayer } from './layers/building-numbers-layer'; import { BuildingHighlightLayer } from './layers/building-highlight-layer'; import { Legend } from './legend'; import SearchBox from './search-box'; import ThemeSwitcher from './theme-switcher'; import BoroughSwitcher from './borough-switcher'; import ParcelSwitcher from './parcel-switcher'; import FloodSwitcher from './flood-switcher'; import ConservationAreaSwitcher from './conservation-switcher'; import HistoricDataSwitcher from './historic-data-switcher'; import { BuildingMapTileset } from '../config/tileserver-config'; interface ColouringMapProps { selectedBuildingId: number; mode: 'basic' | 'view' | 'edit' | 'multi-edit'; category: Category; revisionId: string; onBuildingAction: (building: Building) => void; } export const ColouringMap : FC = ({ category, mode, revisionId, onBuildingAction, selectedBuildingId, children }) => { const [theme, setTheme] = useState('night'); const [borough, setBorough] = useState('enabled'); const [parcel, setParcel] = useState('disabled'); const [flood, setFlood] = useState('disabled'); const [conservation, setConservation] = useState('disabled'); const [historicData, setHistoricData] = useState('disabled'); const [position, setPosition] = useState(initialMapViewport.position); const [zoom, setZoom] = useState(initialMapViewport.zoom); const [mapColourScale, setMapColourScale] = useState(); const handleLocate = useCallback( (lat: number, lng: number, zoom: number) => { setPosition([lat, lng]); setZoom(zoom); }, [] ); const handleClick = useCallback( async (e) => { const {lat, lng} = e.latlng; const data = await apiGet(`/api/buildings/locate?lat=${lat}&lng=${lng}`); const building = data?.[0]; onBuildingAction(building); }, [onBuildingAction], ) const themeSwitch = useCallback( (e) => { e.preventDefault(); const newTheme = (theme === 'light')? 'night' : 'light'; setTheme(newTheme); }, [theme], ) const boroughSwitch = useCallback( (e) => { e.preventDefault(); const newBorough = (borough === 'enabled')? 'disabled' : 'enabled'; setBorough(newBorough); }, [borough], ) const parcelSwitch = useCallback( (e) => { e.preventDefault(); const newParcel = (parcel === 'enabled')? 'disabled' : 'enabled'; setParcel(newParcel); }, [parcel], ) const floodSwitch = useCallback( (e) => { e.preventDefault(); const newFlood = (flood === 'enabled')? 'disabled' : 'enabled'; setFlood(newFlood); }, [flood], ) const conservationSwitch = useCallback( (e) => { e.preventDefault(); const newConservation = (conservation === 'enabled')? 'disabled' : 'enabled'; setConservation(newConservation); }, [conservation], ) const historicDataSwitch = useCallback( (e) => { e.preventDefault(); const newHistoric = (historicData === 'enabled')? 'disabled' : 'enabled'; setHistoricData(newHistoric); }, [historicData], ) const categoryMapDefinitions = useMemo(() => categoryMapsConfig[category], [category]); useEffect(() => { if(!categoryMapDefinitions.some(def => def.mapStyle === mapColourScale)) { setMapColourScale(categoryMapDefinitions[0].mapStyle); } }, [categoryMapDefinitions, mapColourScale]); const hasSelection = selectedBuildingId != undefined; const isEdit = ['edit', 'multi-edit'].includes(mode); return (
{ mapColourScale && } { selectedBuildingId && } { mode !== 'basic' && <> { !hasSelection &&
{isEdit ? 'Click a building to edit' : 'Click a building for details'}
} }
); } function ClickHandler({ onClick }: {onClick: (e) => void}) { useMapEvent('click', (e) => onClick(e)); return null; } function MapBackgroundColor({ theme}: {theme: MapTheme}) { const map = useMap(); useEffect(() => { map.getContainer().style.backgroundColor = mapBackgroundColor[theme]; }); return null; } function MapViewport({ position, zoom }: { position: [number, number]; zoom: number; }) { const map = useMap(); useEffect(() => { map.setView(position, zoom) }, [position, zoom]); return null; }