2021-10-01 08:30:03 -04:00
|
|
|
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
2021-05-04 12:17:14 -04:00
|
|
|
import { AttributionControl, MapContainer, ZoomControl, useMapEvent, Pane, useMap } from 'react-leaflet';
|
2019-02-11 04:04:19 -05:00
|
|
|
|
2019-11-13 14:23:22 -05:00
|
|
|
import 'leaflet/dist/leaflet.css';
|
2019-11-13 14:20:47 -05:00
|
|
|
import './map.css';
|
|
|
|
|
2020-01-02 05:59:13 -05:00
|
|
|
import { apiGet } from '../apiHelpers';
|
2019-08-14 04:21:42 -04:00
|
|
|
import { HelpIcon } from '../components/icons';
|
2021-02-22 01:59:24 -05:00
|
|
|
import { categoryMapsConfig } from '../config/category-maps-config';
|
|
|
|
import { Category } from '../config/categories-config';
|
2021-06-11 20:35:20 -04:00
|
|
|
import { initialMapViewport, mapBackgroundColor, MapTheme } from '../config/map-config';
|
2021-02-22 01:59:24 -05:00
|
|
|
import { Building } from '../models/building';
|
2019-11-07 02:39:26 -05:00
|
|
|
|
2021-05-02 13:24:27 -04:00
|
|
|
import { CityBaseMapLayer } from './layers/city-base-map-layer';
|
|
|
|
import { CityBoundaryLayer } from './layers/city-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';
|
|
|
|
|
2021-10-01 08:30:03 -04:00
|
|
|
import { Legend } from './legend';
|
2021-05-02 13:24:27 -04:00
|
|
|
import SearchBox from './search-box';
|
|
|
|
import ThemeSwitcher from './theme-switcher';
|
2021-10-01 08:30:03 -04:00
|
|
|
import { BuildingMapTileset } from '../config/tileserver-config';
|
2018-09-09 17:22:44 -04:00
|
|
|
|
2019-09-08 20:09:05 -04:00
|
|
|
interface ColouringMapProps {
|
2021-02-22 01:59:24 -05:00
|
|
|
selectedBuildingId: number;
|
2019-09-08 20:09:05 -04:00
|
|
|
mode: 'basic' | 'view' | 'edit' | 'multi-edit';
|
2021-02-22 01:59:24 -05:00
|
|
|
category: Category;
|
|
|
|
revisionId: string;
|
|
|
|
onBuildingAction: (building: Building) => void;
|
2019-09-08 20:09:05 -04:00
|
|
|
}
|
|
|
|
|
2021-10-01 08:30:03 -04:00
|
|
|
export const ColouringMap : FC<ColouringMapProps> = ({
|
|
|
|
category,
|
|
|
|
mode,
|
|
|
|
revisionId,
|
|
|
|
onBuildingAction,
|
|
|
|
selectedBuildingId,
|
|
|
|
children
|
|
|
|
}) => {
|
|
|
|
|
|
|
|
const [theme, setTheme] = useState<MapTheme>('light');
|
|
|
|
const [position, setPosition] = useState(initialMapViewport.position);
|
|
|
|
const [zoom, setZoom] = useState(initialMapViewport.zoom);
|
|
|
|
|
|
|
|
const [mapColourScale, setMapColourScale] = useState<BuildingMapTileset>();
|
|
|
|
|
|
|
|
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 categoryMapDefinitions = useMemo(() => categoryMapsConfig[category], [category]);
|
2021-05-02 13:24:27 -04:00
|
|
|
|
2021-10-01 08:30:03 -04:00
|
|
|
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 (
|
|
|
|
<div className="map-container">
|
|
|
|
<MapContainer
|
|
|
|
center={initialMapViewport.position}
|
|
|
|
zoom={initialMapViewport.zoom}
|
|
|
|
minZoom={9}
|
|
|
|
maxZoom={18}
|
|
|
|
doubleClickZoom={false}
|
|
|
|
zoomControl={false}
|
|
|
|
attributionControl={false}
|
|
|
|
>
|
|
|
|
<ClickHandler onClick={handleClick} />
|
|
|
|
<MapBackgroundColor theme={theme} />
|
|
|
|
<MapViewport position={position} zoom={zoom} />
|
|
|
|
|
|
|
|
<Pane
|
|
|
|
key={theme}
|
|
|
|
name={'cc-base-pane'}
|
|
|
|
style={{zIndex: 50}}
|
2019-05-27 11:39:16 -04:00
|
|
|
>
|
2021-10-01 08:30:03 -04:00
|
|
|
<CityBaseMapLayer theme={theme} />
|
|
|
|
<BuildingBaseLayer theme={theme} />
|
|
|
|
</Pane>
|
|
|
|
|
|
|
|
{
|
|
|
|
mapColourScale &&
|
|
|
|
<BuildingDataLayer
|
|
|
|
tileset={mapColourScale}
|
|
|
|
revisionId={revisionId}
|
|
|
|
/>
|
|
|
|
}
|
2021-05-02 13:24:27 -04:00
|
|
|
|
2021-10-01 08:30:03 -04:00
|
|
|
<Pane
|
|
|
|
name='cc-overlay-pane'
|
|
|
|
style={{zIndex: 300}}
|
|
|
|
>
|
|
|
|
<CityBoundaryLayer />
|
|
|
|
<BuildingNumbersLayer revisionId={revisionId} />
|
2021-05-02 13:24:27 -04:00
|
|
|
{
|
2021-10-01 08:30:03 -04:00
|
|
|
selectedBuildingId &&
|
|
|
|
<BuildingHighlightLayer
|
|
|
|
selectedBuildingId={selectedBuildingId}
|
|
|
|
baseTileset={mapColourScale}
|
2021-12-03 14:32:54 -05:00
|
|
|
/>
|
2021-05-02 13:24:27 -04:00
|
|
|
}
|
2021-10-01 08:30:03 -04:00
|
|
|
</Pane>
|
|
|
|
|
|
|
|
<ZoomControl position="topright" />
|
|
|
|
<AttributionControl prefix=""/>
|
|
|
|
</MapContainer>
|
|
|
|
{
|
|
|
|
mode !== 'basic' &&
|
|
|
|
<>
|
|
|
|
{
|
|
|
|
!hasSelection &&
|
|
|
|
<div className="map-notice">
|
|
|
|
<HelpIcon /> {isEdit ? 'Click a building to edit' : 'Click a building for details'}
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
<Legend mapColourScaleDefinitions={categoryMapDefinitions} mapColourScale={mapColourScale} onMapColourScale={setMapColourScale}/>
|
2021-12-03 14:37:03 -05:00
|
|
|
<ThemeSwitcher onSubmit={themeSwitch} currentTheme={theme} />
|
2021-10-01 08:30:03 -04:00
|
|
|
<SearchBox onLocate={handleLocate} />
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
);
|
2019-05-27 11:20:00 -04:00
|
|
|
}
|
2018-09-09 17:22:44 -04:00
|
|
|
|
2021-12-03 14:32:54 -05:00
|
|
|
function ClickHandler({ onClick }: {onClick: (e) => void}) {
|
2021-04-26 14:19:06 -04:00
|
|
|
useMapEvent('click', (e) => onClick(e));
|
2021-12-03 14:32:54 -05:00
|
|
|
|
2021-04-26 14:19:06 -04:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-12-03 14:32:54 -05:00
|
|
|
function MapBackgroundColor({ theme}: {theme: MapTheme}) {
|
2021-05-04 12:17:14 -04:00
|
|
|
const map = useMap();
|
|
|
|
useEffect(() => {
|
|
|
|
map.getContainer().style.backgroundColor = mapBackgroundColor[theme];
|
|
|
|
});
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-06-11 20:35:20 -04:00
|
|
|
function MapViewport({
|
|
|
|
position,
|
|
|
|
zoom
|
|
|
|
}: {
|
|
|
|
position: [number, number];
|
|
|
|
zoom: number;
|
|
|
|
}) {
|
|
|
|
const map = useMap();
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
map.setView(position, zoom)
|
|
|
|
}, [position, zoom]);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|