Merge branch 'colouring-cities:master' into master

This commit is contained in:
Mike Simpson 2023-04-06 15:51:45 +01:00 committed by GitHub
commit 67d1442206
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 982 additions and 801 deletions

View File

@ -26,6 +26,9 @@ for download under a liberal open data license
## Setup and run ## Setup and run
#### Forking the Core Repository
If you are working with us as part of this GitHub organisation, then a repository and team may already have been set up for you. If you are already usign the repository and want to add a new project, perhaps because you are expanding to additional cities, then please following these instructions to create a Fork of the core repository: [docs/working-with-colouring-core](docs/working-with-colouring-core.md).
#### Customise the Application: #### Customise the Application:
You can customise the Colouring Cities application by changing the values in the following file: You can customise the Colouring Cities application by changing the values in the following file:

View File

@ -623,6 +623,34 @@
<LineSymbolizer stroke="#888" stroke-width="3.0"/> <LineSymbolizer stroke="#888" stroke-width="3.0"/>
</Rule> </Rule>
</Style> </Style>
<Style name="survival_status">
<Rule>
<Filter>[survival_status] = "Same as Historical Map (Unchanged)"</Filter>
<PolygonSymbolizer fill="#6ded45" />
</Rule>
<Rule>
<Filter>[survival_status] = "Similar to Historical Map (Some Changes)"</Filter>
<PolygonSymbolizer fill="#f7c725" />
</Rule>
<Rule>
<Filter>[survival_status] = "Historical Building Demolished"</Filter>
<PolygonSymbolizer fill="#ff2121" />
</Rule>
<Rule>
<Filter>[survival_status] = "Current Building on Previous Green Space"</Filter>
<PolygonSymbolizer fill="#CF26DF" />
</Rule>
<Rule>
<MaxScaleDenominator>17061</MaxScaleDenominator>
<MinScaleDenominator>4264</MinScaleDenominator>
<LineSymbolizer stroke="#888" stroke-width="1.0"/>
</Rule>
<Rule>
<MaxScaleDenominator>4264</MaxScaleDenominator>
<MinScaleDenominator>0</MinScaleDenominator>
<LineSymbolizer stroke="#888" stroke-width="3.0"/>
</Rule>
</Style>
<Style name="likes"> <Style name="likes">
<Rule> <Rule>
<Filter>[likes] &gt;= 100</Filter> <Filter>[likes] &gt;= 100</Filter>

1389
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,11 +20,11 @@
"@mapbox/sphericalmercator": "^1.1.0", "@mapbox/sphericalmercator": "^1.1.0",
"ajv": "^7.1.1", "ajv": "^7.1.1",
"ajv-formats": "^1.5.1", "ajv-formats": "^1.5.1",
"body-parser": "^1.19.0", "body-parser": "^1.20.1",
"bootstrap": "^4.5.0", "bootstrap": "^4.5.0",
"canvas-confetti": "^1.2.0", "canvas-confetti": "^1.2.0",
"connect-pg-simple": "^6.1.0", "connect-pg-simple": "^6.1.0",
"express": "^4.17.1", "express": "^4.18.2",
"express-session": "^1.17.1", "express-session": "^1.17.1",
"geojson": "^0.5.0", "geojson": "^0.5.0",
"leaflet": "^1.7.1", "leaflet": "^1.7.1",
@ -41,7 +41,7 @@
"react-leaflet": "^3.1.0", "react-leaflet": "^3.1.0",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"serialize-javascript": "^5.0.1", "serialize-javascript": "^5.0.1",
"sharp": "^0.28.3", "sharp": "^0.30.5",
"use-throttle": "0.0.3" "use-throttle": "0.0.3"
}, },
"devDependencies": { "devDependencies": {

View File

@ -296,6 +296,14 @@ export const buildingAttributesConfig = valueType<DataFieldConfig>()({ /* eslint
edit: true, edit: true,
verify: true verify: true
}, },
survival_status: {
edit: true,
verify: true
},
survival_source: {
edit: true,
verify: true
},
likes_total: { likes_total: {
edit: false, edit: false,
derivedEdit: true, derivedEdit: true,

View File

@ -20,10 +20,11 @@ import { CategoryViewProps } from './category-view-props';
import { LogicalDataEntry } from '../data-components/logical-data-entry/logical-data-entry'; import { LogicalDataEntry } from '../data-components/logical-data-entry/logical-data-entry';
import { useDisplayPreferences } from '../../displayPreferences-context'; import { useDisplayPreferences } from '../../displayPreferences-context';
const HistoricalStatusOptions = [ const SurvivalStatusOptions = [
'The current footprint matches/almost exactly matches the historical map beneath, and/or is known to have been built before the map was made', 'Same as Historical Map (Unchanged)',
'The building core is the same as the historical map but has had multiple additions/changes', 'Similar to Historical Map (Some Changes)',
'The building no longer exists', 'Historical Building Demolished',
'Current Building on Previous Green Space'
]; ];
/** /**
@ -227,27 +228,33 @@ const AgeView: React.FunctionComponent<CategoryViewProps> = (props) => {
</DataEntryGroup> </DataEntryGroup>
<DataEntryGroup name="Survival and Loss tracked using Historical Maps" collapsed={true} > <DataEntryGroup name="Survival and Loss tracked using Historical Maps" collapsed={true} >
<InfoBox> <InfoBox>
This section is under development. Can you help us create a map that shows how many buildings in London have survived since the 1890s?
Choose a colour to indicate whether the building has survived.
</InfoBox> </InfoBox>
<button className={`map-switcher-inline ${historicData}-state btn btn-outline btn-outline-dark ${darkLightTheme}`} onClick={historicDataSwitchOnClick}> <button className={`map-switcher-inline ${historicData}-state btn btn-outline btn-outline-dark ${darkLightTheme}`} onClick={historicDataSwitchOnClick}>
{(historicData === 'enabled')?'Click here to hide historical maps':'Click here to show historical maps'} {(historicData === 'enabled')?'Click here to hide historical maps':'Click here to show historical maps'}
</button> </button>
<SelectDataEntry <SelectDataEntry
title={dataFields.historical_status.title} title={dataFields.survival_status.title}
slug="historical_status" slug="survival_status"
value={""} value={props.building.survival_status}
tooltip={dataFields.historical_status.tooltip} tooltip={dataFields.survival_status.tooltip}
options={HistoricalStatusOptions} options={SurvivalStatusOptions}
mode={props.mode} mode={props.mode}
copy={props.copy} copy={props.copy}
onChange={props.onChange} onChange={props.onChange}
/> />
<DataEntry <SelectDataEntry
title="Historical land use change" title={dataFields.survival_source.title}
slug="" slug="survival_source"
value="" value={props.building.survival_source}
mode='view' mode={props.mode}
/> copy={props.copy}
onChange={props.onChange}
tooltip={dataFields.survival_source.tooltip}
placeholder={dataFields.survival_source.example}
options={dataFields.survival_source.items}
/>
</DataEntryGroup> </DataEntryGroup>
</Fragment> </Fragment>
); );
@ -496,27 +503,33 @@ const AgeView: React.FunctionComponent<CategoryViewProps> = (props) => {
</DataEntryGroup> </DataEntryGroup>
<DataEntryGroup name="Survival and Loss tracked using Historical Maps" collapsed={true} > <DataEntryGroup name="Survival and Loss tracked using Historical Maps" collapsed={true} >
<InfoBox> <InfoBox>
This section is under development. Can you help us create a map that shows how many buildings in London have survived since the 1890s?
Choose a colour to indicate whether the building has survived.
</InfoBox> </InfoBox>
<button className={`map-switcher-inline ${historicData} btn btn-outline btn-outline-dark ${darkLightTheme}`} onClick={historicDataSwitchOnClick}> <button className={`map-switcher-inline ${historicData}-state btn btn-outline btn-outline-dark ${darkLightTheme}`} onClick={historicDataSwitchOnClick}>
{(historicData === 'enabled')?'Click here to hide historical maps':'Click here to show historical maps'} {(historicData === 'enabled')?'Click here to hide historical maps':'Click here to show historical maps'}
</button> </button>
<SelectDataEntry <SelectDataEntry
title={dataFields.historical_status.title} title={dataFields.survival_status.title}
slug="historical_status" slug="survival_status"
value={""} value={props.building.survival_status}
tooltip={dataFields.historical_status.tooltip} tooltip={dataFields.survival_status.tooltip}
options={HistoricalStatusOptions} options={SurvivalStatusOptions}
mode={props.mode} mode={props.mode}
copy={props.copy} copy={props.copy}
onChange={props.onChange} onChange={props.onChange}
/> />
<DataEntry <SelectDataEntry
title="Historical land use change" title={dataFields.survival_source.title}
slug="" slug="survival_source"
value="" value={props.building.survival_source}
mode='view' mode={props.mode}
/> copy={props.copy}
onChange={props.onChange}
tooltip={dataFields.survival_source.tooltip}
placeholder={dataFields.survival_source.example}
options={dataFields.survival_source.items}
/>
</DataEntryGroup> </DataEntryGroup>
</Fragment> </Fragment>
); );

View File

@ -54,7 +54,7 @@ export const categoriesConfig: {[key in Category]: CategoryDefinition} = {
}, },
[Category.Size]: { [Category.Size]: {
slug: 'size', slug: 'size',
name: 'Size & Form', name: 'Size',
aboutUrl: 'https://pages.colouring.london/shapeandsize', aboutUrl: 'https://pages.colouring.london/shapeandsize',
intro: 'How big are buildings?', intro: 'How big are buildings?',
}, },

View File

@ -24,45 +24,61 @@ export interface CategoryMapDefinition {
export const defaultMapCategory = Category.Age; export const defaultMapCategory = Category.Age;
export const categoryMapsConfig: {[key in Category]: CategoryMapDefinition[]} = { export const categoryMapsConfig: {[key in Category]: CategoryMapDefinition[]} = {
[Category.Age]: [{ [Category.Age]: [
mapStyle: 'date_year', {
legend: { mapStyle: 'date_year',
title: 'Age', legend: {
elements: [ title: 'Age',
{ color: '#fff9b8', text: '>2020' }, elements: [
{ color: '#fae269', text: '2000-2019' }, { color: '#fff9b8', text: '>2020' },
{ color: '#fbaf27', text: '1980-1999' }, { color: '#fae269', text: '2000-2019' },
{ color: '#e6711d', text: '1960-1979' }, { color: '#fbaf27', text: '1980-1999' },
{ color: '#cc1212', text: '1940-1959' }, { color: '#e6711d', text: '1960-1979' },
{ color: '#8f0303', text: '1920-1939' }, { color: '#cc1212', text: '1940-1959' },
{ color: '#8f5385', text: '1900-1919' }, { color: '#8f0303', text: '1920-1939' },
{ color: '#c3e1eb', text: '1880-1899' }, { color: '#8f5385', text: '1900-1919' },
{ color: '#6a9dba', text: '1860-1879' }, { color: '#c3e1eb', text: '1880-1899' },
{ color: '#3b74a3', text: '1840-1859' }, { color: '#6a9dba', text: '1860-1879' },
{ color: '#95ded8', text: '1820-1839' }, { color: '#3b74a3', text: '1840-1859' },
{ color: '#68aba5', text: '1800-1819' }, { color: '#95ded8', text: '1820-1839' },
{ color: '#acc98f', text: '1750-1799' }, { color: '#68aba5', text: '1800-1819' },
{ color: '#6d8a51', text: '1700-1749' }, { color: '#acc98f', text: '1750-1799' },
{ color: '#d0c291', text: '<1700' }, { color: '#6d8a51', text: '1700-1749' },
] { color: '#d0c291', text: '<1700' },
]
},
}, },
}], {
[Category.Size]: [{ mapStyle: 'survival_status',
mapStyle: 'size_height', legend: {
legend: { title: 'Survival status',
title: 'Height to apex', elements: [
elements: [ { color: '#6ded45', text: 'Same as Historical Map (Unchanged)' },
{ color: '#f7f4f9', text: '0-5.55'}, { color: '#f7c725', text: 'Similar to Historical Map (Some Changes)' },
{ color: '#e7e1ef', text: '5.55-7.73'}, { color: '#ff2121', text: 'Historical Building Demolished' },
{ color: '#d4b9da', text: '7.73-11.38'}, { color: '#CF26DF', text: 'Current Building on Previous Green Space' },
{ color: '#c994c7', text: '11.38-18.45'}, ]
{ color: '#df65b0', text: '18.45-35.05'}, }
{ color: '#e7298a', text: '35.05-89.30'},
{ color: '#ce1256', text: '89.30-152'},
{ color: '#980043', text: '≥152'}
]
}, },
}], ],
[Category.Size]: [
{
mapStyle: 'size_height',
legend: {
title: 'Height to apex',
elements: [
{ color: '#f7f4f9', text: '0-5.55'},
{ color: '#e7e1ef', text: '5.55-7.73'},
{ color: '#d4b9da', text: '7.73-11.38'},
{ color: '#c994c7', text: '11.38-18.45'},
{ color: '#df65b0', text: '18.45-35.05'},
{ color: '#e7298a', text: '35.05-89.30'},
{ color: '#ce1256', text: '89.30-152'},
{ color: '#980043', text: '≥152'}
]
},
}
],
[Category.Team]: [{ [Category.Team]: [{
mapStyle: 'team', mapStyle: 'team',
legend: { legend: {

View File

@ -444,7 +444,7 @@ export const dataFields = { /* eslint-disable @typescript-eslint/camelcase */
sust_aggregate_estimate_epc: { sust_aggregate_estimate_epc: {
category: Category.Sustainability, category: Category.Sustainability,
title: "Domestic Building Energy Rating", title: "Domestic Building Energy Rating",
tooltip: "Energy Performance Certificate (EPC) Any premises sold or rented is required to have an EPC to show how energy efficient it is. Only buildings rate grade E or higher maybe rented", tooltip: "Energy Performance Certificate (EPC) Any premises sold or rented is required to have an EPC to show how energy efficient it is. Only buildings rate grade E or higher may be rented",
example: "", example: "",
}, },
sust_retrofit_date: { sust_retrofit_date: {
@ -460,10 +460,37 @@ export const dataFields = { /* eslint-disable @typescript-eslint/camelcase */
//tooltip: , //tooltip: ,
}, },
historical_status: { survival_status: {
category: Category.Age, category: Category.Age,
title: "Historical Status", title: "Survival status",
tooltip: "Survival and Loss tracked using Historical Maps", tooltip: "Survival and Loss tracked using Historical Maps",
items: [
"Same as Historical Map (Unchanged)",
"Similar to Historical Map (Some Changes)",
"Historical Building Demolished",
"Current Building on Previous Green Space"
],
example: "",
},
survival_source: {
category: Category.Age,
title: "Source of survival information",
tooltip: "Source for the survival status",
items: [
"Matched by comparing maps",
"Checked using streetview images",
"Historical publication or archive document",
"Other"
],
example: "",
},
survival_link: {
category: Category.Age,
title: "Please add any additional text or image links providing historical information on this building",
tooltip: "URL for age and date reference",
example: ["", "", ""],
}, },
edit_history: { edit_history: {

View File

@ -21,7 +21,8 @@ export type BuildingMapTileset = 'date_year' |
'building_attachment_form' | 'building_attachment_form' |
'landuse' | 'landuse' |
'dynamics_demolished_count' | 'dynamics_demolished_count' |
'team'; 'team' |
'survival_status';
export type SpecialMapTileset = 'base_light' | 'base_night' | 'base_night_outlines' | 'highlight' | 'number_labels' | 'base_boroughs'; export type SpecialMapTileset = 'base_light' | 'base_night' | 'base_night_outlines' | 'highlight' | 'number_labels' | 'base_boroughs';

View File

@ -3,14 +3,19 @@ import { TileLayer } from 'react-leaflet';
import { LayerEnablementState } from '../../config/map-config'; import { LayerEnablementState } from '../../config/map-config';
import { BuildingBaseLayerAllZoom } from './building-base-layer-all-zoom'; import { BuildingBaseLayerAllZoom } from './building-base-layer-all-zoom';
import { useDisplayPreferences } from '../../displayPreferences-context'; import { useDisplayPreferences } from '../../displayPreferences-context';
import { BuildingDataLayer } from './building-data-layer';
export function HistoricDataLayer({}) { export function HistoricDataLayer({}) {
const { historicData } = useDisplayPreferences(); const { historicData } = useDisplayPreferences();
if(historicData == "enabled") { if(historicData == "enabled") {
return <><TileLayer return <>
url="https://mapseries-tilesets.s3.amazonaws.com/london_1890s/{z}/{x}/{y}.png" <TileLayer
attribution='&copy; CC BY 4.0 - Reproduced with the permission of the <a href="https://maps.nls.uk/">National Library of Scotland</a>' url="https://mapseries-tilesets.s3.amazonaws.com/london_1890s/{z}/{x}/{y}.png"
/><BuildingBaseLayerAllZoom theme="night_outlines" /></> attribution='&copy; CC BY 4.0 - Reproduced with the permission of the <a href="https://maps.nls.uk/">National Library of Scotland</a>'
/>
<BuildingBaseLayerAllZoom theme="night_outlines" />
<BuildingDataLayer tileset={'survival_status'} revisionId={''} />
</>
} else { } else {
return null; return null;
} }

View File

@ -71,44 +71,52 @@ const LAYER_QUERIES = {
WHERE WHERE
construction_core_material IS NOT NULL`, construction_core_material IS NOT NULL`,
location: ` location: `
SELECT SELECT blds_with_data.*
geometry_id, FROM (
( SELECT
case when location_name IS NULL then 0 else 1 end + geometry_id,
case when location_number IS NULL then 0 else 1 end + (
case when location_street IS NULL then 0 else 1 end + case when location_name IS NULL then 0 else 1 end +
case when location_line_two IS NULL then 0 else 1 end + case when location_number IS NULL then 0 else 1 end +
case when location_town IS NULL then 0 else 1 end + case when location_street IS NULL then 0 else 1 end +
case when location_postcode IS NULL then 0 else 1 end + case when location_line_two IS NULL then 0 else 1 end +
case when location_latitude IS NULL then 0 else 1 end + case when location_town IS NULL then 0 else 1 end +
case when location_longitude IS NULL then 0 else 1 end + case when location_postcode IS NULL then 0 else 1 end +
case when ref_toid IS NULL then 0 else 1 end + case when location_latitude IS NULL then 0 else 1 end +
case when ref_osm_id IS NULL then 0 else 1 end case when location_longitude IS NULL then 0 else 1 end +
) AS location_info_count case when ref_toid IS NULL then 0 else 1 end +
FROM case when ref_osm_id IS NULL then 0 else 1 end
buildings`, ) AS location_info_count
FROM
buildings
) AS blds_with_data
WHERE blds_with_data.location_info_count > 0`,
team: ` team: `
SELECT SELECT blds_with_data.*
geometry_id, FROM (
( SELECT
case when has_extension IS NULL then 0 else 1 end + geometry_id,
case when extension_year IS NULL then 0 else 1 end + (
case when developer_type IS NULL then 0 else 1 end + case when has_extension IS NULL then 0 else 1 end +
case when developer_name IS NULL then 0 else 1 end + case when extension_year IS NULL then 0 else 1 end +
case when developer_source_link IS NULL then 0 else 1 end + case when developer_type IS NULL then 0 else 1 end +
case when designers IS NULL then 0 else 1 end + case when developer_name IS NULL then 0 else 1 end +
case when designers_source_link IS NULL then 0 else 1 end + case when developer_source_link IS NULL then 0 else 1 end +
case when lead_designer_type IS NULL then 0 else 1 end + case when designers IS NULL then 0 else 1 end +
case when designer_awards IS NULL then 0 else 1 end + case when designers_source_link IS NULL then 0 else 1 end +
case when awards_source_link IS NULL then 0 else 1 end + case when lead_designer_type IS NULL then 0 else 1 end +
case when builder IS NULL then 0 else 1 end + case when designer_awards IS NULL then 0 else 1 end +
case when builder_source_link IS NULL then 0 else 1 end + case when awards_source_link IS NULL then 0 else 1 end +
case when other_team IS NULL then 0 else 1 end + case when builder IS NULL then 0 else 1 end +
case when other_team_source_link IS NULL then 0 else 1 end + case when builder_source_link IS NULL then 0 else 1 end +
case when date_year IS NULL then 0 else 1 end case when other_team IS NULL then 0 else 1 end +
) AS team_info_count case when other_team_source_link IS NULL then 0 else 1 end +
FROM case when date_year IS NULL then 0 else 1 end
buildings`, ) AS team_info_count
FROM
buildings
) AS blds_with_data
WHERE blds_with_data.team_info_count > 0`,
is_domestic: ` is_domestic: `
SELECT SELECT
geometry_id, geometry_id,
@ -117,6 +125,22 @@ const LAYER_QUERIES = {
buildings buildings
WHERE WHERE
is_domestic IS NOT NULL`, is_domestic IS NOT NULL`,
survival_status: `
SELECT
geometry_id,
survival_status
FROM
buildings
WHERE
survival_status IS NOT NULL`,
survival_source: `
SELECT
geometry_id,
survival_source
FROM
buildings
WHERE
survival_source IS NOT NULL`,
likes: ` likes: `
SELECT SELECT
geometry_id, geometry_id,

View File

@ -0,0 +1,25 @@
# Working with the Colouring Cities Core Platform
To make Colouring Cities a better and more collaborative platform, we strongly encourage all of our partners to keep their code as unified with the [Core Platform](https://github.com/colouring-cities/colouring-core) as possible. This will help ensure consitency and enable data sharing between the various Colouring Cities projects, and will also allow us all to continue to improve the Core Platform.
## Working with Forks
By using Forks, GitHub will maintain a link between your project and the Core plaform. This will allow you to see any changes that are made to the Core respository and easily syncrhonise those changes into your own code, as shown below (as well as allowing you to contribute some of your code back to the Core platform).
## Forking the Core Respository
We recommend that you create a "Fork" of the [colouring-core](https://github.com/colouring-cities/colouring-core) to create the respository for your Colouring Cities project, and then customise the code from the core platform to suit the needs of your projects.
This can be done by going to the [Core Repository](https://github.com/colouring-cities/colouring-core), clicking the 'Fork' button, as show below and then following the instructions.
![image](https://user-images.githubusercontent.com/21125422/224355892-7cc76027-6402-4fb1-b12b-7c41e3e4e55d.png)
## Checking Project Status
On your project repository, you will then see that your project is forked from Colouring Core, and you will also be notified when changes are made to the Core platform, as shown in the example below.
![image](https://user-images.githubusercontent.com/21125422/224358529-4ef5ac1d-50e2-467b-95aa-033e44cce7af.png)
You can click on "Sync Fork" and then "Update Branch" to pull the latest changes from Colouring Core into your own project.
## Customising your Colouring Cities Project
When it comes to customising the Colouring Core platform for use in your country/city, we want to make this as easy as possible, while still maintaining the brand identity of the platform.
Where possible, customisations should be made using configuration files, rather than by modifying the source code. More information on the current configuration file setup can be found [here](configuring-colouring-cities.md). Please let us know (ideally by creating issues on the [Core respository](https://github.com/colouring-cities/colouring-core) (if there aree other elements that you would like us to add into this configuration framework.

View File

@ -1,4 +1,8 @@
Following instructions assume that code is placed within `~/colouring-london/etl/planning_data/` Planning data was written to be extendable to other locations but currently is setup only for London and relies on GLA data.
Extending it to other locations will require some custom work. At least connecting to other external API and parsing their data. Some additional custom data processing will be likely needed like it was needed for London data.
Following instructions assume that code is placed within `~/colouring-core/etl/planning_data/` and tiles are within `/srv/colouring-london/tilecache/` Note that this is affected by location specified in ecosystem config file which may be for example at `/var/www/colouring-london/ecosystem.config.js`
To install necessary dependencies use `cd ~/colouring-london/etl/planning_data/ && pip3 install -r requirements.txt` To install necessary dependencies use `cd ~/colouring-london/etl/planning_data/ && pip3 install -r requirements.txt`

View File

@ -56,7 +56,9 @@ COPY (SELECT
planning_historic_area_assessment_url, planning_historic_area_assessment_url,
is_domestic, is_domestic,
community_type_worth_keeping_total, community_type_worth_keeping_total,
likes_total likes_total,
survival_status,
survival_source
FROM buildings) FROM buildings)
TO '/tmp/building_attributes.csv' TO '/tmp/building_attributes.csv'
WITH CSV HEADER WITH CSV HEADER

View File

@ -0,0 +1,2 @@
ALTER TABLE buildings DROP COLUMN IF EXISTS survival_status;
ALTER TABLE buildings DROP COLUMN IF EXISTS survival_source;

View File

@ -0,0 +1,2 @@
ALTER TABLE buildings ADD COLUMN IF NOT EXISTS survival_status text;
ALTER TABLE buildings ADD COLUMN IF NOT EXISTS survival_source text;