colouring-montreal/app/src/frontend/map/map.tsx

186 lines
6.5 KiB
TypeScript
Raw Normal View History

2019-09-04 10:05:41 -04:00
import { LatLngExpression } from 'leaflet';
2019-05-27 13:26:29 -04:00
import PropTypes from 'prop-types';
2019-09-04 10:05:41 -04:00
import React, { Component, Fragment } from 'react';
import { Map, TileLayer, ZoomControl, AttributionControl } from 'react-leaflet-universal';
import '../../../node_modules/leaflet/dist/leaflet.css'
import './map.css'
2019-02-11 04:04:19 -05:00
import { HelpIcon } from '../components/icons';
2019-02-11 04:04:19 -05:00
import Legend from './legend';
import { parseCategoryURL } from '../../parse';
2019-02-11 04:04:19 -05:00
import SearchBox from './search-box';
import ThemeSwitcher from './theme-switcher';
const OS_API_KEY = 'NVUxtY5r8eA6eIfwrPTAGKrAAsoeI9E9';
2019-09-04 10:05:41 -04:00
interface ColouringMapState {
theme: 'light' | 'night';
lat: number;
lng: number;
zoom: number;
}
/**
* Map area
*/
2019-09-04 10:05:41 -04:00
class ColouringMap extends Component<any, ColouringMapState> { // TODO: add proper types
static propTypes = { // TODO: generate propTypes from TS
building: PropTypes.object,
revision_id: PropTypes.number,
selectBuilding: PropTypes.func,
colourBuilding: PropTypes.func,
match: PropTypes.object,
history: PropTypes.object
};
2018-09-10 07:40:25 -04:00
constructor(props) {
super(props);
this.state = {
2018-09-27 16:37:32 -04:00
theme: 'night',
2018-09-10 07:40:25 -04:00
lat: 51.5245255,
lng: -0.1338422,
2018-09-10 18:34:56 -04:00
zoom: 16
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);
2019-05-09 04:16:36 -04:00
this.getMode = this.getMode.bind(this);
2018-09-10 07:40:25 -04:00
}
2019-02-11 04:04:19 -05:00
handleLocate(lat, lng, zoom){
this.setState({
lat: lat,
lng: lng,
zoom: zoom
})
}
2019-05-09 04:16:36 -04:00
getMode() {
2019-05-27 13:26:29 -04:00
const isEdit = this.props.match.url.match('edit')
2019-05-09 04:16:36 -04:00
const isMulti = this.props.match.url.match('multi')
return isEdit? (isMulti? 'multi' : 'edit') : 'view';
}
handleClick(e) {
const mode = this.getMode()
2018-11-29 17:00:53 -05:00
const lat = e.latlng.lat
const lng = e.latlng.lng
2019-05-27 13:26:29 -04:00
const newCat = parseCategoryURL(this.props.match.url);
const mapCat = newCat || 'age';
2018-09-10 07:40:25 -04:00
fetch(
2019-08-14 09:05:49 -04:00
'/api/buildings/locate?lat='+lat+'&lng='+lng
2018-09-10 07:40:25 -04:00
).then(
(res) => res.json()
).then(function(data){
2018-09-30 16:54:47 -04:00
if (data && data.length){
const building = data[0];
2019-05-09 04:16:36 -04:00
if (mode === 'multi') {
// colour building directly
this.props.colourBuilding(building);
} else {
this.props.selectBuilding(building);
2019-05-27 15:13:43 -04:00
this.props.history.push(`/${mode}/${mapCat}/building/${building.building_id}.html`);
2019-05-09 04:16:36 -04:00
}
2018-09-10 07:40:25 -04:00
} else {
// deselect but keep/return to expected colour theme
this.props.selectBuilding(undefined);
2019-05-27 13:26:29 -04:00
this.props.history.push(`/${mode}/${mapCat}.html`);
2018-09-10 07:40:25 -04:00
}
2018-09-10 18:34:56 -04:00
}.bind(this)).catch(
(err) => console.error(err)
)
}
2018-09-13 11:03:49 -04:00
themeSwitch(e) {
e.preventDefault();
const newTheme = (this.state.theme === 'light')? 'night' : 'light';
this.setState({theme: newTheme});
}
render() {
2019-09-04 10:05:41 -04:00
const position: LatLngExpression = [this.state.lat, this.state.lng];
2018-09-13 18:55:53 -04:00
// baselayer
const key = OS_API_KEY
const tilematrixSet = 'EPSG:3857'
2018-09-13 11:03:49 -04:00
const layer = (this.state.theme === 'light')? 'Light 3857' : 'Night 3857';
const url = `https://api2.ordnancesurvey.co.uk/mapping_api/v1/service/zxy/${tilematrixSet}/${layer}/{z}/{x}/{y}.png?key=${key}`
const attribution = 'Building attribute data is © Colouring London contributors. Maps contain OS data © Crown copyright: OS Maps baselayers and building outlines.'
2018-09-10 17:14:09 -04:00
2018-09-13 18:55:53 -04:00
// colour-data tiles
2019-05-27 13:26:29 -04:00
const isBuilding = /building/.test(this.props.match.url);
const isEdit = /edit/.test(this.props.match.url);
2018-11-30 04:26:28 -05:00
const cat = parseCategoryURL(this.props.match.url);
2019-05-27 13:26:29 -04:00
const tilesetByCat = {
age: 'date_year',
size: 'size_storeys',
location: 'location',
like: 'likes',
planning: 'conservation_area',
}
2019-05-27 13:26:29 -04:00
const tileset = tilesetByCat[cat];
// pick revision id to bust browser cache
2019-05-09 04:16:36 -04:00
const rev = this.props.revision_id;
2019-05-27 15:13:43 -04:00
const dataLayer = tileset?
2018-12-05 15:39:16 -05:00
<TileLayer
2019-05-27 13:26:29 -04:00
key={tileset}
url={`/tiles/${tileset}/{z}/{x}/{y}.png?rev=${rev}`}
2019-02-24 10:29:39 -05:00
minZoom={9} />
: null;
2018-09-10 18:34:56 -04:00
2018-09-13 18:55:53 -04:00
// highlight
2019-05-27 13:26:29 -04:00
const geometryId = (this.props.building) ? this.props.building.geometry_id : undefined;
const highlight = `/tiles/highlight/{z}/{x}/{y}.png?highlight=${geometryId}`
const highlightLayer = (isBuilding && this.props.building) ?
2018-12-05 15:39:16 -05:00
<TileLayer
key={this.props.building.building_id}
url={highlight}
minZoom={14} />
: null;
2018-09-10 17:14:09 -04:00
2019-05-27 13:26:29 -04:00
const baseUrl = (this.state.theme === 'light')?
2019-05-27 11:31:48 -04:00
'/tiles/base_light/{z}/{x}/{y}.png'
: '/tiles/base_night/{z}/{x}/{y}.png'
return (
2018-09-13 15:35:27 -04:00
<Fragment>
<Map
center={position}
zoom={this.state.zoom}
2019-02-24 10:29:39 -05:00
minZoom={9}
2018-09-13 15:35:27 -04:00
maxZoom={18}
doubleClickZoom={false}
zoomControl={false}
attributionControl={false}
onClick={this.handleClick}
2019-05-27 11:39:16 -04:00
>
2018-09-13 15:35:27 -04:00
<TileLayer url={url} attribution={attribution} />
2019-05-27 13:26:29 -04:00
<TileLayer url={baseUrl} minZoom={14} />
2018-09-13 18:55:53 -04:00
{ dataLayer }
2018-09-13 15:35:27 -04:00
{ highlightLayer }
<ZoomControl position="topright" />
<AttributionControl prefix="" />
</Map>
2019-01-19 14:06:26 -05:00
{
2019-05-27 13:26:29 -04:00
!isBuilding && this.props.match.url !== '/'? (
2019-01-22 14:39:16 -05:00
<div className="map-notice">
2019-05-27 13:26:29 -04:00
<HelpIcon /> {isEdit? 'Click a building to edit' : 'Click a building for details'}
2019-01-19 14:06:26 -05:00
</div>
) : null
}
2019-02-11 04:04:19 -05:00
{
this.props.match.url !== '/'? (
2019-05-27 11:39:16 -04:00
<Fragment>
<Legend slug={cat} />
<ThemeSwitcher onSubmit={this.themeSwitch} currentTheme={this.state.theme} />
2019-05-27 13:26:29 -04:00
<SearchBox onLocate={this.handleLocate} isBuilding={isBuilding} />
2019-05-27 11:39:16 -04:00
</Fragment>
2019-02-11 04:04:19 -05:00
) : null
}
2018-09-13 15:35:27 -04:00
</Fragment>
);
}
2019-05-27 11:20:00 -04:00
}
export default ColouringMap;