Merge branch 'master' into feature/new-categories-menu

This commit is contained in:
Mike Simpson 2023-01-17 10:54:47 +00:00
commit ef09fa3b78
24 changed files with 155 additions and 93 deletions

View File

@ -3,7 +3,6 @@
justify-content: center;
align-items: center;
box-sizing: border-box;
/* padding: 0.1em; */
width: 100%;
height: 100%;
}
@ -18,5 +17,6 @@
text-align: center;
font-size: 1em;
margin: 0;
padding: 0.25em;
}

View File

@ -17,7 +17,7 @@
.section-header .h2 {
display: inline-block;
flex-basis: 150px;
flex-basis: 200px;
flex-shrink: 0;
flex-grow: 1;
margin: 0.75rem 0 0.5em 0.1em;
@ -33,7 +33,8 @@
.section-header .section-header-actions {
display: inline-block;
flex-basis: 400px;
flex-basis: 220px;
flex-shrink: 0;
display: flex;
flex-flow: row wrap;
align-items: center;

View File

@ -99,7 +99,7 @@ const PlanningDataOfficialDataEntry: React.FC<PlanningDataOfficialDataEntryProps
<Fragment>
<InfoBox type='success'>
<Fragment>
<div><b>Current planning application status for this site:</b> <StatusInfo
<div><b>Planning application status for this site:</b> <StatusInfo
statusBeforeAliasing={item["status_before_aliasing"]}
status={item["status"]}
/></div>

View File

@ -13,7 +13,7 @@ import { CategoryViewProps } from './category-view-props';
*/
const StreetscapeView: React.FunctionComponent<CategoryViewProps> = (props) => (
<Fragment>
<InfoBox msg="This is what we're planning to collect on the building's context" />
<InfoBox type='warning' msg="This is what we're planning to collect on the building's context" />
<ul className="data-list">
<li>Gardens</li>
<li>Trees</li>

View File

@ -120,7 +120,7 @@ export const buildingUserFields = {
export const dataFields = { /* eslint-disable @typescript-eslint/camelcase */
location_name: {
category: Category.Location,
title: "Building information (link)",
title: "Building Name (Information link)",
tooltip: "Link to a website with information on the building, not needed for most.",
example: "https://en.wikipedia.org/wiki/Palace_of_Westminster",
},

View File

@ -3,6 +3,9 @@ import React, { createContext, useCallback, useContext, useEffect, useState } fr
import { LayerEnablementState, MapTheme } from './config/map-config';
interface DisplayPreferencesContextState {
resetLayers: (e: React.FormEvent<HTMLFormElement>) => void;
anyLayerModifiedState: () => boolean;
vista: LayerEnablementState;
vistaSwitch: (e: React.FormEvent<HTMLFormElement>) => void;
vistaSwitchOnClick: React.MouseEventHandler<HTMLButtonElement>;
@ -45,6 +48,9 @@ const stub = (): never => {
};
export const DisplayPreferencesContext = createContext<DisplayPreferencesContextState>({
resetLayers: stub,
anyLayerModifiedState: stub,
vista: undefined,
vistaSwitch: stub,
vistaSwitchOnClick: undefined,
@ -85,16 +91,69 @@ export const DisplayPreferencesContext = createContext<DisplayPreferencesContext
const noop = () => {};
export const DisplayPreferencesProvider: React.FC<{}> = ({children}) => {
const [vista, setVista] = useState<LayerEnablementState>('disabled');
const [flood, setFlood] = useState<LayerEnablementState>('disabled');
const [creative, setCreative] = useState<LayerEnablementState>('disabled');
const [housing, setHousing] = useState<LayerEnablementState>('disabled');
const [borough, setBorough] = useState<LayerEnablementState>('enabled');
const [parcel, setParcel] = useState<LayerEnablementState>('disabled');
const [conservation, setConservation] = useState<LayerEnablementState>('disabled');
const [historicData, setHistoricData] = useState<LayerEnablementState>('disabled');
const defaultVista = 'disabled'
const defaultFlood = 'disabled'
const defaultCreative = 'disabled'
const defaultHousing = 'disabled'
const defaultBorough = 'enabled'
const defaultParcel = 'disabled'
const defaultConservation = 'disabled'
const defaultHistoricData = 'disabled'
const [vista, setVista] = useState<LayerEnablementState>(defaultVista);
const [flood, setFlood] = useState<LayerEnablementState>(defaultFlood);
const [creative, setCreative] = useState<LayerEnablementState>(defaultCreative);
const [housing, setHousing] = useState<LayerEnablementState>(defaultHousing);
const [borough, setBorough] = useState<LayerEnablementState>(defaultBorough);
const [parcel, setParcel] = useState<LayerEnablementState>(defaultParcel);
const [conservation, setConservation] = useState<LayerEnablementState>(defaultConservation);
const [historicData, setHistoricData] = useState<LayerEnablementState>(defaultHistoricData);
const [darkLightTheme, setDarkLightTheme] = useState<MapTheme>('night');
const resetLayers = useCallback(
(e) => {
e.preventDefault();
setVista(defaultVista);
setFlood(defaultFlood);
setCreative(defaultCreative);
setHousing(defaultHousing);
setBorough(defaultBorough)
setParcel(defaultParcel);
setConservation(defaultConservation);
setHistoricData(defaultHistoricData);
//setDarkLightTheme('night'); // reset only layers
},
[]
)
function anyLayerModifiedState() {
if(vista != defaultVista) {
return true;
}
if(flood != defaultFlood) {
return true;
}
if(creative != defaultCreative) {
return true;
}
if(housing != defaultHousing) {
return true;
}
if(borough != defaultBorough) {
return true;
}
if(parcel != defaultParcel) {
return true;
}
if(conservation != defaultConservation) {
return true;
}
if(historicData != defaultHistoricData) {
return true;
}
//darkLightTheme not handled here
return false;
}
const vistaSwitch = useCallback(
(e) => {
e.preventDefault();
@ -233,6 +292,9 @@ export const DisplayPreferencesProvider: React.FC<{}> = ({children}) => {
return (
<DisplayPreferencesContext.Provider value={{
resetLayers,
anyLayerModifiedState,
vista,
vistaSwitch,
vistaSwitchOnClick,

View File

@ -16,22 +16,13 @@ export function BoroughBoundaryLayer({}) {
if(borough == "enabled") {
return boundaryGeojson &&
<><GeoJSON
<GeoJSON
attribution='Borough boundary from <a href=https://data.london.gov.uk/dataset/london_boroughs>London Datastore</a> Ordnance Survey Open Data - Contains public sector information licensed under the <a href=https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/>Open Government Licence v3.0</a>'
data={boundaryGeojson}
style={{color: '#f00', fill: false, weight: 1}}
/* minNativeZoom={17}*/
/><BuildingBaseLayerAllZoom theme="boroughs" /></>;
} else if (borough == "disabled") {
return <div></div>
// do not display anything
return boundaryGeojson &&
<GeoJSON
data={boundaryGeojson}
style={{color: '#0f0', fill: false, weight: 1}} />
/>;
} else {
return boundaryGeojson &&
<GeoJSON data={boundaryGeojson} style={{color: '#0f0', fill: true}}/>;
return <></>
}
}

View File

@ -0,0 +1,17 @@
import { GeoJsonObject } from 'geojson';
import React, { useEffect, useState } from 'react';
import { GeoJSON } from 'react-leaflet';
import { useDisplayPreferences } from '../../displayPreferences-context';
import { apiGet } from '../../apiHelpers';
import { BuildingBaseLayerAllZoom } from './building-base-layer-all-zoom';
export function BoroughLabelLayer({}) {
const { borough } = useDisplayPreferences();
if(borough == "enabled") {
return <BuildingBaseLayerAllZoom theme="boroughs" />;
} else {
return <></>
}
}

View File

@ -35,6 +35,14 @@
}
}
.reset-switcher {
top: 37px;
margin-right: 60px;
}
.reset-switcher .btn {
min-width: 280px;
}
.theme-switcher {
top: 77px;
}

View File

@ -28,19 +28,6 @@
.leaflet-grab {
cursor: crosshair;
}
.map-notice {
position: absolute;
top: 3.5rem;
left: 0.5rem;
z-index: 1000;
padding: 0.5rem 0.75rem;
width: 250px;
background: #fff;
border: 1px solid #fff;
border-radius: 4px;
box-shadow: 0px 0px 1px 1px #222;
display: none;
}
@media (min-width: 990px){
/* Only show the "Click a building ..." notice for larger screens */
.map-notice {

View File

@ -14,6 +14,7 @@ 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 { BoroughLabelLayer } from './layers/borough-label-layer';
import { ParcelBoundaryLayer } from './layers/parcel-boundary-layer';
import { HistoricDataLayer } from './layers/historic-data-layer';
import { FloodBoundaryLayer } from './layers/flood-boundary-layer';
@ -30,6 +31,7 @@ import { Legend } from './legend';
import SearchBox from './search-box';
import ThemeSwitcher from './theme-switcher';
import DataLayerSwitcher from './data-switcher';
import { ResetSwitcher } from './reset-switcher';
import { BoroughSwitcher } from './borough-switcher';
import { ParcelSwitcher } from './parcel-switcher';
import { FloodSwitcher } from './flood-switcher';
@ -158,6 +160,12 @@ export const ColouringMap : FC<ColouringMapProps> = ({
/>
}
</Pane>
<Pane
name='cc-label-overlay-pane'
style={{zIndex: 1000}}
>
<BoroughLabelLayer/>
</Pane>
<ZoomControl position="topright" />
<AttributionControl prefix=""/>
@ -165,13 +173,8 @@ export const ColouringMap : FC<ColouringMapProps> = ({
{
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={onMapColourScale}/>
<ResetSwitcher/>
<ThemeSwitcher onSubmit={darkLightThemeSwitch} currentTheme={darkLightTheme} />
<DataLayerSwitcher onSubmit={layerSwitch} currentDisplay={dataLayers} />
{

View File

@ -0,0 +1,20 @@
import React from 'react';
import './map-button.css';
import { useDisplayPreferences } from '../displayPreferences-context';
export const ResetSwitcher: React.FC<{}> = () => {
const { resetLayers, darkLightTheme, anyLayerModifiedState } = useDisplayPreferences();
if(anyLayerModifiedState()) {
return (
<form className={`reset-switcher map-button ${darkLightTheme}`} onSubmit={resetLayers}>
<button className="btn btn-outline btn-outline-dark"
type="submit">
{"Reset layers"}
</button>
</form>
);
} else {
return <></>
}
}

View File

@ -103,10 +103,7 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
apiGet(`/api/search?q=${this.state.q}`)
.then((data) => {
if (data && data.results){
this.setState({
results: data.results,
fetching: false
});
this.props.onLocate(data.results[0].geometry.coordinates[1], data.results[0].geometry.coordinates[0], data.results[0].attributes.zoom)
} else {
console.error(data);
@ -157,31 +154,7 @@ class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
);
}
const resultsList = this.state.results.length?
<ul className="search-box-results">
{
this.state.results.map((result) => {
const label = result.attributes.label;
const lng = result.geometry.coordinates[0];
const lat = result.geometry.coordinates[1];
const zoom = result.attributes.zoom;
const href = `?lng=${lng}&lat=${lat}&zoom=${zoom}`;
return (
<li key={result.attributes.label}>
<a
className="search-box-result"
onClick={(e) => {
e.preventDefault();
this.props.onLocate(lat, lng, zoom);
}}
href={href}
>{`${label.substring(0, 4)} ${label.substring(4, 7)}`}</a>
</li>
);
})
}
</ul>
: null;
const resultsList = null;
return (
<div className="search-box" onKeyDown={this.handleKeyPress}>
<div className="search-box-pane">

View File

@ -39,7 +39,7 @@ export const AuthRoute: React.FC<RouteProps> = ({ component: Component, children
if(isAuthenticated) {
let state = props.location.state as any;
let from = '/my-account.html';
if (typeof state == 'object' && 'from' in state){
if (typeof state == 'object' && state !== null && 'from' in state) {
from = state.from;
}

View File

@ -53,7 +53,7 @@ const tileCache = new TileCache(
shouldCacheFn,
// don't clear on bounding box cache clear tilesets not affected by user-editable data
(tileset: string) => tileset !== 'base_light' && tileset !== 'base_night' && tileset !== 'base_night_outlines' && tileset !== 'base_burough' && tileset !== "planning_applications_status_recent" && tileset !== "planning_applications_status_very_recent" && tileset !== "planning_applications_status_all"
(tileset: string) => tileset !== 'base_light' && tileset !== 'base_night' && tileset !== 'base_night_outlines' && tileset !== 'base_borough' && tileset !== "planning_applications_status_recent" && tileset !== "planning_applications_status_very_recent" && tileset !== "planning_applications_status_all"
);
const renderBuildingTile = (t: TileParams, d: any) => renderDataSourceTile(t, d, getDataConfig, getLayerVariables);

View File

@ -1,15 +1,15 @@
-- Remove sustainability fields, update in paralell with adding new fields
-- Remove sustainability fields, update in parallel with adding new fields
-- BREEAM rating
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_breeam_rating;
-- BREEAM date
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_breeam_date;
-- DEC (display energy certifcate, only applies to non domestic buildings)
-- DEC (display energy certificate, only applies to non domestic buildings)
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_dec;
-- DEC date
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_dec_date;
--DEC certifcate lmk key, this would be lmkkey, no online lookup but can scrape through API. Numeric (25)
--DEC certificate lmk key, this would be lmkkey, no online lookup but can scrape through API. Numeric (25)
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_dec_lmkey;
-- Aggregate EPC rating (Estimated) for a building, derived from inidividual certificates

View File

@ -1,4 +1,4 @@
-- Remove sustainability fields, update in paralell with adding new fields
-- Remove sustainability fields, update in parallel with adding new fields
-- Last significant retrofit date YYYY
-- Need to add a constraint to sust_retrofit_date
-- Renewal technologies
@ -6,7 +6,7 @@
-- Values:
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_renewables_tech;
--Has a building had a major renovation without extenstion (captured in form)
--Has a building had a major renovation without extension (captured in form)
--Boolean yes/no - links to the the DATE
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_retrofitted;

View File

@ -20,7 +20,7 @@ ALTER TABLE buildings
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS sust_breeam_date smallint;
-- DEC (display energy certifcate, only applies to non domestic buildings)
-- DEC (display energy certificate, only applies to non domestic buildings)
-- A - G
CREATE TYPE sust_dec
AS ENUM ('A',

View File

@ -1,10 +1,10 @@
-- Remove sustainability fields, update in paralell with adding new fields
-- Remove sustainability fields, update in parallel with adding new fields
-- Last significant retrofit date YYYY
-- Need to add a constraint to sust_retrofit_date
ALTER TABLE buildings
ADD CONSTRAINT sust_retrofit_date_end CHECK (sust_retrofit_date <= DATE_PART('year', CURRENT_DATE));
--Has a building had a major renovation without extenstion (captured in form)
--Has a building had a major renovation without extension (captured in form)
--Boolean yes/no - links to the the DATE
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS sust_retrofitted boolean DEFAULT 'n';

View File

@ -1,4 +1,4 @@
--Landuse is hierachical. Highest level is Order (ie. Residential) then Group (ie Residential-Dwelling) then Class (ie Residential-Dwelling-Detached house)
--Landuse is hierarchical. Highest level is Order (ie. Residential) then Group (ie Residential-Dwelling) then Class (ie Residential-Dwelling-Detached house)
--Interface will collected most detailed (class) but visualise highest level (order)
--Landuse is a table as #358
--Land use class, group and order will be stored in a new table

View File

@ -1,5 +1,5 @@
-- Create land use and fields
--Landuse is hierachical. Highest level is Order (Residential) then Group (Residential-Dwelling) then Class (Residential-Dwelling-Detached house)
--Landuse is hierarchical. Highest level is Order (Residential) then Group (Residential-Dwelling) then Class (Residential-Dwelling-Detached house)
--Some ETL work required to get this together refer to analysis repo
--Prerequesite is to have first run bulk_data_sources migrations
--Then create table landuse_order for the app, this is used as foreign key for current and original landuse_order
@ -36,11 +36,11 @@ FROM reference_tables.landuse_classifications a
WHERE a.level = 'class'
AND a.is_used;
--Landuse is hierachical. Highest level is Order (Residential) then Group (Residential-Dwelling) then Class (Residential-Dwelling-Detached house)
--Landuse is hierarchical. Highest level is Order (Residential) then Group (Residential-Dwelling) then Class (Residential-Dwelling-Detached house)
--Interface will collected most detailed (class) but visualise highest level (order)
--Landuse is a table as #358
--Prerequisite run bulk_sources migration first
-- Land use is table with 3 levels of hierachy (highest to lowest). order > group > class
-- Land use is table with 3 levels of hierarchy (highest to lowest). order > group > class
-- Land use order, singular. Client and db constrained with foreign key

View File

@ -3,7 +3,7 @@
-- -- Ownership type, enumerate type from:
-- ALTER TABLE buildings DROP COLUMN IF EXISTS ownership_type;
-- -- Ownerhsip perception, would you describe this as a community asset?
-- -- Ownership perception, would you describe this as a community asset?
-- -- Boolean yes / no
-- ALTER TABLE buildings DROP COLUMN IF EXISTS ownership_perception;

View File

@ -1,3 +1,3 @@
-- Remove team fields, update in paralell with adding new fields
-- Remove team fields, update in parallel with adding new fields
-- Award or awards (may be multiple) stored as json b object
ALTER TABLE buildings DROP COLUMN IF EXISTS team_awards;

View File

@ -3,6 +3,6 @@
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS team_awards jsonb;
--To validate this input, the following confirms it's an valid object but not that the items in the object are validated agains those we will acccept
--To validate this input, the following confirms it's an valid object but not that the items in the object are validated against those we will accept
ALTER TABLE buildings
ADD CONSTRAINT data_is_valid CHECK (is_jsonb_valid ('{"type": "object"}', team_awards));