From 0848ba5246abf7b77aaec71a30d3f0141b3c6104 Mon Sep 17 00:00:00 2001 From: Maciej Ziarkowski Date: Sun, 2 May 2021 18:24:27 +0100 Subject: [PATCH] Fix TileLayer url bug, reorganise map layers --- .../frontend/config/category-maps-config.ts | 3 +- app/src/frontend/config/map-config.ts | 7 + app/src/frontend/config/tileserver-config.ts | 19 +++ .../map/layers/building-base-layer.tsx | 19 +++ .../map/layers/building-data-layer.tsx | 16 +++ .../map/layers/building-highlight-layer.tsx | 16 +++ .../map/layers/building-numbers-layer.tsx | 14 ++ .../map/layers/city-base-map-layer.tsx | 33 +++++ .../map/layers/city-boundary-layer.tsx | 17 +++ .../frontend/map/layers/get-tile-layer-url.ts | 14 ++ app/src/frontend/map/map.tsx | 127 +++++++----------- 11 files changed, 205 insertions(+), 80 deletions(-) create mode 100644 app/src/frontend/config/map-config.ts create mode 100644 app/src/frontend/config/tileserver-config.ts create mode 100644 app/src/frontend/map/layers/building-base-layer.tsx create mode 100644 app/src/frontend/map/layers/building-data-layer.tsx create mode 100644 app/src/frontend/map/layers/building-highlight-layer.tsx create mode 100644 app/src/frontend/map/layers/building-numbers-layer.tsx create mode 100644 app/src/frontend/map/layers/city-base-map-layer.tsx create mode 100644 app/src/frontend/map/layers/city-boundary-layer.tsx create mode 100644 app/src/frontend/map/layers/get-tile-layer-url.ts diff --git a/app/src/frontend/config/category-maps-config.ts b/app/src/frontend/config/category-maps-config.ts index 35f3cef2..19c60d84 100644 --- a/app/src/frontend/config/category-maps-config.ts +++ b/app/src/frontend/config/category-maps-config.ts @@ -1,4 +1,5 @@ import { Category } from './categories-config'; +import { BuildingMapTileset } from './tileserver-config'; export type LegendElement = { color: string; @@ -16,7 +17,7 @@ export interface LegendConfig { } export interface CategoryMapDefinition { - mapStyle: string; + mapStyle: BuildingMapTileset; legend: LegendConfig; } diff --git a/app/src/frontend/config/map-config.ts b/app/src/frontend/config/map-config.ts new file mode 100644 index 00000000..fc818656 --- /dev/null +++ b/app/src/frontend/config/map-config.ts @@ -0,0 +1,7 @@ +export const defaultMapPosition = { + lat: 51.5245255, + lng: -0.1338422, + zoom: 16 +}; + +export type MapTheme = 'light' | 'night'; \ No newline at end of file diff --git a/app/src/frontend/config/tileserver-config.ts b/app/src/frontend/config/tileserver-config.ts new file mode 100644 index 00000000..e3745443 --- /dev/null +++ b/app/src/frontend/config/tileserver-config.ts @@ -0,0 +1,19 @@ +/** + * This file defines all the valid tileset names that can be obtained from the tilserver. + * Adjust the values here if modifying the list of styles in the tileserver. + */ + +export type BuildingMapTileset = 'date_year' | + 'size_height' | + 'construction_core_material' | + 'location' | + 'likes' | + 'planning_combined' | + 'sust_dec' | + 'building_attachment_form' | + 'landuse' | + 'dynamics_demolished_count'; + +export type SpecialMapTileset = 'base_light' | 'base_night' | 'highlight' | 'number_labels'; + +export type MapTileset = BuildingMapTileset | SpecialMapTileset; diff --git a/app/src/frontend/map/layers/building-base-layer.tsx b/app/src/frontend/map/layers/building-base-layer.tsx new file mode 100644 index 00000000..c491102b --- /dev/null +++ b/app/src/frontend/map/layers/building-base-layer.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { TileLayer } from 'react-leaflet'; + +import { MapTheme } from '../../config/map-config'; +import { MapTileset } from '../../config/tileserver-config'; + +import {getTileLayerUrl } from './get-tile-layer-url'; + +export function BuildingBaseLayer({ theme }: {theme: MapTheme}) { + const tileset = `base_${theme}` as const; + + return ; +} diff --git a/app/src/frontend/map/layers/building-data-layer.tsx b/app/src/frontend/map/layers/building-data-layer.tsx new file mode 100644 index 00000000..c841d205 --- /dev/null +++ b/app/src/frontend/map/layers/building-data-layer.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { TileLayer } from 'react-leaflet'; + +import { BuildingMapTileset } from '../../config/tileserver-config'; + +import {getTileLayerUrl } from './get-tile-layer-url'; + +export function BuildingDataLayer({tileset, revisionId} : { tileset: BuildingMapTileset, revisionId: string }) { + return ; +} \ No newline at end of file diff --git a/app/src/frontend/map/layers/building-highlight-layer.tsx b/app/src/frontend/map/layers/building-highlight-layer.tsx new file mode 100644 index 00000000..b958f563 --- /dev/null +++ b/app/src/frontend/map/layers/building-highlight-layer.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import { TileLayer } from 'react-leaflet'; + +import { BuildingMapTileset } from '../../config/tileserver-config'; + +import { getTileLayerUrl } from './get-tile-layer-url'; + +export function BuildingHighlightLayer({selectedBuildingId, baseTileset}: {selectedBuildingId: number, baseTileset: BuildingMapTileset}) { + return ; +} diff --git a/app/src/frontend/map/layers/building-numbers-layer.tsx b/app/src/frontend/map/layers/building-numbers-layer.tsx new file mode 100644 index 00000000..76f8eb2e --- /dev/null +++ b/app/src/frontend/map/layers/building-numbers-layer.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import { TileLayer } from 'react-leaflet'; + +import {getTileLayerUrl } from './get-tile-layer-url'; + +export function BuildingNumbersLayer({revisionId}: {revisionId: string}) { + return ; +} diff --git a/app/src/frontend/map/layers/city-base-map-layer.tsx b/app/src/frontend/map/layers/city-base-map-layer.tsx new file mode 100644 index 00000000..db6582a7 --- /dev/null +++ b/app/src/frontend/map/layers/city-base-map-layer.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; +import { TileLayer } from 'react-leaflet'; + +import { MapTheme } from '../../config/map-config'; + +const OS_API_KEY = 'NVUxtY5r8eA6eIfwrPTAGKrAAsoeI9E9'; + +/** + * Base raster layer for the map. + * @param theme map theme + */ +export function CityBaseMapLayer({theme}: {theme: MapTheme}) { + + /** + * Ordnance Survey maps - UK / London specific + * (replace with appropriate base map for other cities/countries) + */ + const key = OS_API_KEY; + const tilematrixSet = 'EPSG:3857'; + const layer = theme === 'light' ? 'Light 3857' : 'Night 3857'; + const baseUrl = `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. OS licence'; + + return ; +} + diff --git a/app/src/frontend/map/layers/city-boundary-layer.tsx b/app/src/frontend/map/layers/city-boundary-layer.tsx new file mode 100644 index 00000000..534b0611 --- /dev/null +++ b/app/src/frontend/map/layers/city-boundary-layer.tsx @@ -0,0 +1,17 @@ +import { GeoJsonObject } from 'geojson'; +import React, { useEffect, useState } from 'react'; +import { GeoJSON } from 'react-leaflet'; + +import { apiGet } from '../../apiHelpers'; + +export function CityBoundaryLayer() { + const [boundaryGeojson, setBoundaryGeojson] = useState(null); + + useEffect(() => { + apiGet('/geometries/boundary-detailed.geojson') + .then(data => setBoundaryGeojson(data as GeoJsonObject)); + }, []); + + return boundaryGeojson && + ; +} \ No newline at end of file diff --git a/app/src/frontend/map/layers/get-tile-layer-url.ts b/app/src/frontend/map/layers/get-tile-layer-url.ts new file mode 100644 index 00000000..f8a97817 --- /dev/null +++ b/app/src/frontend/map/layers/get-tile-layer-url.ts @@ -0,0 +1,14 @@ +import { MapTileset } from '../../config/tileserver-config'; + +/** + * Formats a CL tileserver URL for a tileset and a set of parameters + * @param tileset the name of the tileset + * @param parameters (optional) dictionary of parameter values + * @returns A string with the formatted URL + */ +export function getTileLayerUrl(tileset: T, parameters?: Record) { + let paramString = parameters && new URLSearchParams(parameters).toString(); + paramString = paramString == undefined ? '' : `?${paramString}`; + + return `/tiles/${tileset}/{z}/{x}/{y}{r}.png${paramString}`; +} \ No newline at end of file diff --git a/app/src/frontend/map/map.tsx b/app/src/frontend/map/map.tsx index 8ace26d0..32ed24d5 100644 --- a/app/src/frontend/map/map.tsx +++ b/app/src/frontend/map/map.tsx @@ -1,21 +1,26 @@ -import { GeoJsonObject } from 'geojson'; import React, { Component, Fragment } from 'react'; -import { AttributionControl, GeoJSON, MapContainer, TileLayer, ZoomControl, useMapEvent } from 'react-leaflet'; +import { AttributionControl, MapContainer, ZoomControl, useMapEvent, Pane } 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 { defaultMapPosition, MapTheme } 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 { 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 { categoryMapsConfig } from '../config/category-maps-config'; -import { Category } from '../config/categories-config'; -import { Building } from '../models/building'; - -const OS_API_KEY = 'NVUxtY5r8eA6eIfwrPTAGKrAAsoeI9E9'; interface ColouringMapProps { selectedBuildingId: number; @@ -26,12 +31,12 @@ interface ColouringMapProps { } interface ColouringMapState { - theme: 'light' | 'night'; + theme: MapTheme; lat: number; lng: number; zoom: number; - boundary: GeoJsonObject; } + /** * Map area */ @@ -40,10 +45,7 @@ class ColouringMap extends Component { super(props); this.state = { theme: 'night', - lat: 51.5245255, - lng: -0.1338422, - zoom: 16, - boundary: undefined, + ...defaultMapPosition }; this.handleClick = this.handleClick.bind(this); this.handleLocate = this.handleLocate.bind(this); @@ -73,72 +75,12 @@ class ColouringMap extends Component { this.setState({theme: newTheme}); } - async getBoundary() { - const data = await apiGet('/geometries/boundary-detailed.geojson') as GeoJsonObject; - - this.setState({ - boundary: data - }); - } - - componentDidMount() { - this.getBoundary(); - } - render() { const categoryMapDefinition = categoryMapsConfig[this.props.category]; const position: [number, number] = [this.state.lat, this.state.lng]; - // baselayer - const key = OS_API_KEY; - const tilematrixSet = 'EPSG:3857'; - const layer = (this.state.theme === 'light')? 'Light 3857' : 'Night 3857'; - const baseUrl = `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. OS licence'; - const baseLayer = ; - - const buildingsBaseUrl = `/tiles/base_${this.state.theme}/{z}/{x}/{y}{r}.png`; - const buildingBaseLayer = ; - - const boundaryLayer = this.state.boundary && - ; - const tileset = categoryMapDefinition.mapStyle; - const dataLayer = tileset != undefined && - ; - - // highlight - const highlightLayer = this.props.selectedBuildingId != undefined && - ; - - const numbersLayer = const hasSelection = this.props.selectedBuildingId != undefined; const isEdit = ['edit', 'multi-edit'].includes(this.props.mode); @@ -155,12 +97,39 @@ class ColouringMap extends Component { attributionControl={false} > - { baseLayer } - { buildingBaseLayer } - { boundaryLayer } - { dataLayer } - { highlightLayer } - { numbersLayer } + + + + + + + { + tileset && + + } + + + + + { + this.props.selectedBuildingId && + + } + +