2021-05-04 12:17:14 -04:00
|
|
|
import React, { Component, Fragment, useEffect } from 'react';
|
|
|
|
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';
|
|
|
|
|
|
|
|
import Legend from './legend';
|
|
|
|
import SearchBox from './search-box';
|
|
|
|
import ThemeSwitcher from './theme-switcher';
|
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
|
|
|
}
|
|
|
|
|
2019-09-04 10:05:41 -04:00
|
|
|
interface ColouringMapState {
|
2021-05-02 13:24:27 -04:00
|
|
|
theme: MapTheme;
|
2021-06-11 20:35:20 -04:00
|
|
|
position: [number, number];
|
2019-09-04 10:05:41 -04:00
|
|
|
zoom: number;
|
|
|
|
}
|
2021-05-02 13:24:27 -04:00
|
|
|
|
2018-09-09 17:22:44 -04:00
|
|
|
/**
|
|
|
|
* Map area
|
|
|
|
*/
|
2019-10-17 12:07:34 -04:00
|
|
|
class ColouringMap extends Component<ColouringMapProps, ColouringMapState> {
|
2018-09-10 07:40:25 -04:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
2021-05-06 13:53:57 -04:00
|
|
|
theme: 'light',
|
2021-06-11 20:35:20 -04:00
|
|
|
...initialMapViewport
|
2018-09-10 07:40:25 -04:00
|
|
|
};
|
|
|
|
this.handleClick = this.handleClick.bind(this);
|
2019-02-11 04:04:19 -05:00
|
|
|
this.handleLocate = this.handleLocate.bind(this);
|
2018-09-13 11:03:49 -04:00
|
|
|
this.themeSwitch = this.themeSwitch.bind(this);
|
2018-09-10 07:40:25 -04:00
|
|
|
}
|
|
|
|
|
2021-06-11 20:35:20 -04:00
|
|
|
handleLocate(lat: number, lng: number, zoom: number){
|
2019-02-11 04:04:19 -05:00
|
|
|
this.setState({
|
2021-06-11 20:35:20 -04:00
|
|
|
position: [lat, lng],
|
2019-02-11 04:04:19 -05:00
|
|
|
zoom: zoom
|
2019-09-08 20:09:05 -04:00
|
|
|
});
|
2019-05-09 04:16:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
handleClick(e) {
|
2020-01-02 05:59:13 -05:00
|
|
|
const { lat, lng } = e.latlng;
|
|
|
|
apiGet(`/api/buildings/locate?lat=${lat}&lng=${lng}`)
|
2021-02-22 01:59:24 -05:00
|
|
|
.then(data => {
|
|
|
|
const building = data?.[0];
|
|
|
|
this.props.onBuildingAction(building);
|
|
|
|
}).catch(err => console.error(err));
|
2018-09-09 17:22:44 -04:00
|
|
|
}
|
|
|
|
|
2018-09-13 11:03:49 -04:00
|
|
|
themeSwitch(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
const newTheme = (this.state.theme === 'light')? 'night' : 'light';
|
|
|
|
this.setState({theme: newTheme});
|
|
|
|
}
|
|
|
|
|
2018-09-09 17:22:44 -04:00
|
|
|
render() {
|
2021-02-22 01:59:24 -05:00
|
|
|
const categoryMapDefinition = categoryMapsConfig[this.props.category];
|
|
|
|
|
|
|
|
const tileset = categoryMapDefinition.mapStyle;
|
2021-01-28 21:40:11 -05:00
|
|
|
|
2021-02-22 01:59:24 -05:00
|
|
|
const hasSelection = this.props.selectedBuildingId != undefined;
|
2019-09-08 20:09:05 -04:00
|
|
|
const isEdit = ['edit', 'multi-edit'].includes(this.props.mode);
|
2018-10-25 05:16:18 -04:00
|
|
|
|
2018-09-09 17:22:44 -04:00
|
|
|
return (
|
2019-09-04 12:18:45 -04:00
|
|
|
<div className="map-container">
|
2021-04-26 14:19:06 -04:00
|
|
|
<MapContainer
|
2021-06-11 20:35:20 -04:00
|
|
|
center={initialMapViewport.position}
|
|
|
|
zoom={initialMapViewport.zoom}
|
2019-02-24 10:29:39 -05:00
|
|
|
minZoom={9}
|
2021-06-16 17:41:01 -04:00
|
|
|
maxZoom={18}
|
2018-09-13 15:35:27 -04:00
|
|
|
doubleClickZoom={false}
|
|
|
|
zoomControl={false}
|
|
|
|
attributionControl={false}
|
2019-05-27 11:39:16 -04:00
|
|
|
>
|
2021-04-26 14:19:06 -04:00
|
|
|
<ClickHandler onClick={this.handleClick} />
|
2021-05-04 12:17:14 -04:00
|
|
|
<MapBackgroundColor theme={this.state.theme} />
|
2021-06-11 20:35:20 -04:00
|
|
|
<MapViewport position={this.state.position} zoom={this.state.zoom} />
|
2021-05-02 13:24:27 -04:00
|
|
|
|
|
|
|
<Pane
|
|
|
|
key={this.state.theme}
|
|
|
|
name={'cc-base-pane'}
|
|
|
|
style={{zIndex: 50}}
|
|
|
|
>
|
2021-05-06 13:53:57 -04:00
|
|
|
<CityBaseMapLayer theme={this.state.theme} />
|
2021-05-02 13:24:27 -04:00
|
|
|
<BuildingBaseLayer theme={this.state.theme} />
|
|
|
|
</Pane>
|
|
|
|
|
|
|
|
{
|
|
|
|
tileset &&
|
|
|
|
<BuildingDataLayer
|
|
|
|
tileset={tileset}
|
|
|
|
revisionId={this.props.revisionId}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
|
|
|
|
<Pane
|
|
|
|
name='cc-overlay-pane'
|
|
|
|
style={{zIndex: 300}}
|
|
|
|
>
|
|
|
|
<CityBoundaryLayer />
|
|
|
|
<BuildingNumbersLayer revisionId={this.props.revisionId} />
|
|
|
|
{
|
|
|
|
this.props.selectedBuildingId &&
|
|
|
|
<BuildingHighlightLayer
|
|
|
|
selectedBuildingId={this.props.selectedBuildingId}
|
|
|
|
baseTileset={tileset}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
</Pane>
|
|
|
|
|
2018-09-13 15:35:27 -04:00
|
|
|
<ZoomControl position="topright" />
|
2019-09-09 07:35:03 -04:00
|
|
|
<AttributionControl prefix=""/>
|
2021-04-26 14:19:06 -04:00
|
|
|
</MapContainer>
|
2019-01-19 14:06:26 -05:00
|
|
|
{
|
2021-02-22 01:59:24 -05:00
|
|
|
this.props.mode !== 'basic' &&
|
|
|
|
<Fragment>
|
|
|
|
{
|
|
|
|
!hasSelection &&
|
|
|
|
<div className="map-notice">
|
|
|
|
<HelpIcon /> {isEdit ? 'Click a building to edit' : 'Click a building for details'}
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
<Legend legendConfig={categoryMapDefinition?.legend} />
|
2021-05-06 13:53:57 -04:00
|
|
|
{/* <ThemeSwitcher onSubmit={this.themeSwitch} currentTheme={this.state.theme} /> */}
|
2021-02-22 01:59:24 -05:00
|
|
|
<SearchBox onLocate={this.handleLocate} />
|
|
|
|
</Fragment>
|
2019-02-11 04:04:19 -05:00
|
|
|
}
|
2019-09-04 12:18:45 -04:00
|
|
|
</div>
|
2018-09-09 17:22:44 -04:00
|
|
|
);
|
|
|
|
}
|
2019-05-27 11:20:00 -04:00
|
|
|
}
|
2018-09-09 17:22:44 -04:00
|
|
|
|
2021-04-26 14:19:06 -04:00
|
|
|
function ClickHandler({ onClick }: {onClick: (e) => void}) {
|
|
|
|
useMapEvent('click', (e) => onClick(e));
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-05-04 12:17:14 -04:00
|
|
|
function MapBackgroundColor({ theme}: {theme: MapTheme}) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-09-09 17:22:44 -04:00
|
|
|
export default ColouringMap;
|