Caclulate tree visibility statistics per district¶
Author: Willeke A’Campo
Description: This notebooks shows how to calculate the Ecosystem Service statistics for tree visibility and impact per district using DuckDB. The results are stored in a new table in the database and exported to GeoJSON.
Documentation:
Data conversion | GeoJSON to GeoParquet¶
[1]:
import geopandas as gpd
from shapely.geometry import Point
from shapely.wkb import loads
import pyarrow
import os
import leafmap
import os
import duckdb
import pandas as pd
# TODO move to kedro pipeline
municipality = "kristiansand"
raw_dir = r"/workspaces/urban-climate/data/01_raw"
interim_dir = r"/workspaces/urban-climate/data/02_intermediate"
reporting_dir = r"/workspaces/urban-climate/data/08_reporting/"
db_path = os.path.join(raw_dir, f"{municipality}.db")
# Define the table names
file_names = [
f"{municipality}_study_area",
f"{municipality}_districts",
f"{municipality}_bldg",
f"{municipality}_res_bldg",
f"{municipality}_green_space",
f"{municipality}_open_space",
f"{municipality}_public_open_space",
f"{municipality}_private_open_space",
f"{municipality}_tree_crowns"
]
table_names = [
"study_area", "districts", "bldg", "res_bldg", "green_space",
"open_space", "public_open_space", "private_open_space", "tree_crowns"
]
# Define the parquet_dict
parquet_dict = {
name: os.path.join(interim_dir, f"{name}.parquet")
for name in file_names}
# Check if the parquet files exist, if not convert to parquet
for key in parquet_dict.keys():
if os.path.exists(parquet_dict[key]):
continue
else:
# Define the gdf_dict
gdf_dict = {
name: gpd.read_file(os.path.join(raw_dir, f"{name}.geojson"))
for name in file_names}
# Convert GeoDataFrame to Parquet
for key, gdf in gdf_dict.items():
gdf.to_parquet(
path = interim_dir + "/" + key + ".parquet",
index = None,
compression = "snappy"
)
[2]:
# Create a connection to the DuckDB database
con = duckdb.connect(database=db_path, read_only=False)
con.install_extension("spatial")
con.load_extension("spatial")
# Create a table for each parquet file or GeoDataFrame
for key,table in zip(parquet_dict.keys(), table_names):
con.execute(
f"""
CREATE TABLE {table}
AS SELECT *, ST_GeomFromWKB(geometry)
FROM parquet_scan('{parquet_dict[key]}')
"""
)
# Fetch and print all table names
result = con.execute(
"""
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'main'
"""
)
print(result.fetchall())
[('study_area',), ('districts',), ('bldg',), ('res_bldg',), ('green_space',), ('open_space',), ('public_open_space',), ('private_open_space',), ('tree_crowns',)]
Create a new Table with Tree Crown Center Points
[19]:
# Check if the 'crowns' table exists
table_exists = "tree_crowns" in [
row[0]
for row in con.execute(
"""
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'main'
"""
).fetchall()
]
if table_exists:
# convert dtype to DuckDB GEOMETRY
result = con.execute(
"""
SELECT
ST_X(ST_Centroid(ST_GeomFromWKB(geometry))),
ST_Y(ST_Centroid(ST_GeomFromWKB(geometry)))
FROM tree_crowns"""
)
# xy_crowns to pd
df = pd.DataFrame(result.fetchall(), columns=["X", "Y"])
xy_crowns = gpd.GeoDataFrame(
df,
geometry= gpd.points_from_xy(df.X, df.Y)
)
xy_crowns.crs = "EPSG:25832"
# Create a new table in DuckDB
con.execute(
"""
CREATE TABLE tree_crowns_xy AS
SELECT
ST_X(ST_Centroid(ST_GeomFromWKB(geometry))) AS X,
ST_Y(ST_Centroid(ST_GeomFromWKB(geometry))) AS Y,
ST_Point(ST_X(ST_Centroid(ST_GeomFromWKB(geometry))), ST_Y(ST_Centroid(ST_GeomFromWKB(geometry)))) AS geometry
FROM tree_crowns"""
)
Map study area and Tree Crown Center Points (10%-sample)
[ ]:
# add layers to gdf for mapping
gdf_study_area = leafmap.read_parquet(
parquet_dict[f"{municipality}_study_area"],
return_type='gdf',
src_crs="EPSG:25832",
dst_crs="EPSG:4326"
)
# convert xy_crowns to wgs84
xy_crowns_sample = xy_crowns.sample(frac=0.05)
trees_xy = xy_crowns_sample.to_crs("EPSG:4326")
points_geojson = trees_xy.__geo_interface__
print("Map the tree crown center points (10% sample).")
print(trees_xy.head(2))
# Calculate the center of the study_area GeoDataFrame
center = gdf_study_area.geometry.unary_union.centroid
# --------------------------------------------------
# INIT MAP
# --------------------------------------------------
map = leafmap.Map()
# center
map.set_center(center.x, center.y, zoom=13)
# add Basemap
map.add_basemap("CartoDB.Positron")
# add study area as vector layer
map.add_gdf(
gdf_study_area,
layer_name="study_area",
get_fill_color=[0, 0, 255, 128]
)
map.add_gdf(
trees_xy,
layer_name ="trees",
color= "black"
)
map
Database Connection | DuckDB¶
[3]:
#%%capture
columns = con.execute("PRAGMA table_info(districts)").fetchall()
for column in columns:
print(column[1])
OBJECTID
fylkesnummer
fylkesnavn
kommunenummer
kommunenavn
delomradenummer
delomradenavn
grunnkretsnummer
grunnkretsnavn
kilde_admin
kilde_befolkning
id_befolkning
year_pop_stat
pop_total
pop_elderly
a_district
a_unit
a_clipped
SHAPE_Length
SHAPE_Area
geometry
st_geomfromwkb(geometry)
Split open space, buildings and tree crowns by district
[4]:
%%capture
# create new table split_open_space
# with open space split by district boundaries
con.execute(
"""
CREATE TABLE split_open_space AS
SELECT
districts.grunnkretsnummer,
ST_Intersection(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(open_space.geometry)) AS geom
FROM
districts, open_space
WHERE
ST_Intersects(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(open_space.geometry));
"""
)
[5]:
%%capture
# create new table split_private_space
# with public space split by district boundaries
con.execute(
"""
CREATE TABLE split_private_space AS
SELECT
districts.grunnkretsnummer,
ST_Intersection(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(private_open_space.geometry)) AS geom
FROM
districts, private_open_space
WHERE
ST_Intersects(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(private_open_space.geometry));
"""
)
[6]:
%%capture
# create new table split_public_space
# with private space split by district boundaries
con.execute(
"""
CREATE TABLE split_public_space AS
SELECT
districts.grunnkretsnummer,
ST_Intersection(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(public_open_space.geometry)) AS geom
FROM
districts, public_open_space
WHERE
ST_Intersects(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(public_open_space.geometry));
"""
)
[7]:
%%capture
# create new table split_buildings
# with buildings split by district boundaries
con.execute(
"""
CREATE TABLE split_bldg AS
SELECT
districts.grunnkretsnummer,
ST_Intersection(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(bldg.geometry)) AS geom
FROM
districts, bldg
WHERE
ST_Intersects(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(bldg.geometry));
"""
)
[9]:
%%capture
# create new table split_res_bldg
# with buildings split by district boundaries
con.execute(
"""
CREATE TABLE split_res_bldg AS
SELECT
districts.grunnkretsnummer,
ST_Intersection(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(res_bldg.geometry)) AS geom
FROM
districts, res_bldg
WHERE
ST_Intersects(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(res_bldg.geometry));
"""
)
---------------------------------------------------------------------------
CatalogException Traceback (most recent call last)
Cell In[9], line 3
1 # create new table split_res_bldg
2 # with buildings split by district boundaries
----> 3 con.execute(
4 """
5 CREATE TABLE split_res_bldg AS
6 SELECT
7 districts.grunnkretsnummer,
8 ST_Intersection(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(res_bldg.geometry)) AS geom
9 FROM
10 districts, res_bldg
11 WHERE
12 ST_Intersects(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(res_bldg.geometry));
13 """
14 )
CatalogException: Catalog Error: Table with name "split_res_bldg" already exists!
[10]:
%%capture
# create new table split_tree_crowns
# with tree crowns split by district boundaries
con.execute(
"""
CREATE TABLE split_tree_crowns AS
SELECT
districts.grunnkretsnummer,
ST_Intersection(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(tree_crowns.geometry)) AS geom
FROM
districts, tree_crowns
WHERE
ST_Intersects(ST_GeomFromWKB(districts.geometry), ST_GeomFromWKB(tree_crowns.geometry));
"""
)
[11]:
%%capture
# print columns split_open_space
columns = con.execute("PRAGMA table_info(split_open_space)").fetchall()
for column in columns:
print(column[1])
# Query to fetch all table names
tables = con.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
# Print the first row of each table
for table in tables:
first_row = con.execute(f"SELECT * FROM {table[0]} LIMIT 1").fetchone()
print(f"First row of {table[0]}: {first_row}")
Generate Columns with Count Statistics¶
Name |
Alias |
Description |
Type |
Unit |
---|---|---|---|---|
n_trees |
Antall trær |
Number of trees in the district |
INT |
|
n_bldg |
Antall bygninger |
Number of buildings in the district |
INT |
|
n_res_bldg |
Antall boliger |
Number of residential buildings in the district |
INT |
|
n_res_bldg_near_gs |
Antall boliger nær grøntområde (300 m) |
Number of residential buildings near green space (300 m) |
INT |
|
n_bldg_near_trees |
Antall trær nær boliger (15 m) |
Number of trees near residential buildings (15 m) |
INT |
|
n_viewshed |
Antall viewshed piksler |
Number of viewshed pixels that intersect with the building edge |
INT |
Add columns to districts table
[13]:
# update districts
# add count columns: n_trees, n_bldg, n_res_bldg, n_res_bldg_near_gs, n_bldg_near_trees
con.execute("ALTER TABLE districts ADD COLUMN n_trees INTEGER")
con.execute("ALTER TABLE districts ADD COLUMN n_bldg INTEGER")
con.execute("ALTER TABLE districts ADD COLUMN n_res_bldg INTEGER")
con.execute("ALTER TABLE districts ADD COLUMN n_green_spaces INTEGER")
con.execute("ALTER TABLE districts ADD COLUMN n_res_bldg_near_gs INTEGER")
con.execute("ALTER TABLE districts ADD COLUMN perc_near_gs INTEGER")
con.execute("ALTER TABLE districts ADD COLUMN n_near_trees INTEGER")
con.execute("ALTER TABLE districts ADD COLUMN n_bldg_near_trees INTEGER")
con.execute("ALTER TABLE districts ADD COLUMN perc_near_trees INTEGER")
---------------------------------------------------------------------------
CatalogException Traceback (most recent call last)
Cell In[13], line 3
1 # update districts
2 # add count columns: n_trees, n_bldg, n_res_bldg, n_res_bldg_near_gs, n_bldg_near_trees
----> 3 con.execute("ALTER TABLE districts ADD COLUMN n_trees INTEGER")
4 con.execute("ALTER TABLE districts ADD COLUMN n_bldg INTEGER")
5 con.execute("ALTER TABLE districts ADD COLUMN n_res_bldg INTEGER")
CatalogException: Catalog Error: Column with name n_trees already exists!
[17]:
con.execute("ALTER TABLE res_bldg ADD COLUMN n_trees INTEGER")
con.execute("ALTER TABLE res_bldg ADD COLUMN n_green_spaces INTEGER")
[17]:
<duckdb.duckdb.DuckDBPyConnection at 0x7f55e03490b0>
[ ]:
%%capture
# print columns districts
columns = con.execute("PRAGMA table_info(split_bldg)").fetchall()
for column in columns:
print(column[1])
Calculate COUNT attributes
[22]:
# COUNT number of trees per district
n_trees = con.execute(
"""
SELECT districts.grunnkretsnummer, COUNT(*)
FROM tree_crowns_xy
JOIN districts ON ST_Within(tree_crowns_xy.geometry, ST_GeomFromWKB(districts.geometry))
GROUP BY districts.grunnkretsnummer
"""
).fetchall()
# COUNT number of buildings per district
n_bldg = con.execute(
"""
SELECT districts.grunnkretsnummer, COUNT(*)
FROM split_bldg
JOIN districts ON ST_Within(split_bldg.geom, ST_GeomFromWKB(districts.geometry))
GROUP BY districts.grunnkretsnummer
"""
).fetchall()
# COUNT number of residential buildings per district
n_res_bldg = con.execute(
"""
SELECT districts.grunnkretsnummer, COUNT(*)
FROM split_res_bldg
JOIN districts ON ST_Within(split_res_bldg.geom, ST_GeomFromWKB(districts.geometry))
GROUP BY districts.grunnkretsnummer
"""
).fetchall()
# COUNT number of res buildings WITHIN 300m distance of green space
n_res_bldg_near_gs = con.execute(
"""
SELECT districts.grunnkretsnummer, COUNT(*)
FROM res_bldg
JOIN districts ON ST_Within(ST_GeomFromWKB(res_bldg.geometry), ST_GeomFromWKB(districts.geometry))
WHERE res_bldg.geometry IS NOT NULL AND districts.geometry IS NOT NULL AND EXISTS (
SELECT 1
FROM green_space
WHERE green_space.geometry IS NOT NULL AND ST_DWithin(ST_GeomFromWKB(res_bldg.geometry), ST_GeomFromWKB(green_space.geometry), 300)
)
GROUP BY districts.grunnkretsnummer
"""
).fetchall()
# COUNT
# count all trees within 15m distance of a res bldg
# if count >= 3, set n_trees to 1
# if count < 3, set n_trees to 0
n_near_trees = con.execute(
f"""
UPDATE res_bldg
SET n_trees = (
CASE
WHEN (
SELECT COUNT(*)
FROM tree_crowns
WHERE tree_crowns.geometry IS NOT NULL
AND ST_DWithin(ST_GeomFromWKB(res_bldg.geometry), ST_GeomFromWKB(tree_crowns.geometry), 15)
) >= 3 THEN 1
ELSE 0
END
)
"""
).fetchall()
# COUNT number of res buildings with value=1 in n_trees column
# add to districts table
n_res_bldg_near_trees = con.execute(
"""
SELECT districts.grunnkretsnummer, COUNT(*)
FROM res_bldg
JOIN districts ON ST_Within(ST_GeomFromWKB(res_bldg.geometry), ST_GeomFromWKB(districts.geometry))
WHERE res_bldg.n_trees = 1
GROUP BY districts.grunnkretsnummer
"""
).fetchall()
[23]:
# Update districts table
for id, count in n_trees:
con.execute(f"UPDATE districts SET n_trees = {count} WHERE grunnkretsnummer = {id}")
for id, count in n_bldg:
con.execute(f"UPDATE districts SET n_bldg = {count} WHERE grunnkretsnummer = {id}")
for id, count in n_res_bldg:
con.execute(f"UPDATE districts SET n_res_bldg = {count} WHERE grunnkretsnummer = {id}")
for id, count in n_res_bldg_near_gs:
con.execute(f"UPDATE districts SET n_res_bldg_near_gs = {count} WHERE grunnkretsnummer = {id}")
for id, count in n_res_bldg_near_trees:
print(id, count)
con.execute(f"UPDATE districts SET n_bldg_near_trees = {count} WHERE grunnkretsnummer = {id}")
# print columns districts
columns = con.execute("PRAGMA table_info(districts)").fetchall()
for column in columns:
print(column[1])
42040904 67
42040807 1
42040702 223
42040908 29
42040910 72
42040907 124
42040701 59
42040803 65
42040804 105
42041104 1
42040918 8
42040705 52
42040704 33
42040902 178
42040903 94
42040806 28
42040919 1
42040909 18
42040911 121
42040905 134
42040802 23
42041103 12
42040915 1
42041101 21
42040912 15
42040913 52
42040901 71
42040801 33
OBJECTID
fylkesnummer
fylkesnavn
kommunenummer
kommunenavn
delomradenummer
delomradenavn
grunnkretsnummer
grunnkretsnavn
kilde_admin
kilde_befolkning
id_befolkning
year_pop_stat
pop_total
pop_elderly
a_district
a_unit
a_clipped
SHAPE_Length
SHAPE_Area
geometry
st_geomfromwkb(geometry)
n_trees
n_bldg
n_res_bldg
n_res_bldg_near_gs
perc_near_gs
n_bldg_near_trees
perc_near_trees
n_green_spaces
n_near_trees
[24]:
# if NAN set to 0
con.execute(
"""
UPDATE districts
SET n_res_bldg_near_gs = COALESCE(n_res_bldg_near_gs, 0)
"""
)
con.execute(
"""
UPDATE districts
SET n_bldg = COALESCE(n_bldg, 0)
"""
)
con.execute(
"""
UPDATE districts
SET n_res_bldg = COALESCE(n_res_bldg, 0)
"""
)
# normalize perc_near_gs
con.execute(
"""
UPDATE districts
SET perc_near_gs = (n_res_bldg_near_gs / n_res_bldg) * 100
"""
)
# normalize perc_near_trees
con.execute(
"""
UPDATE districts
SET perc_near_trees = (n_bldg_near_trees / n_res_bldg) * 100
"""
)
[24]:
<duckdb.duckdb.DuckDBPyConnection at 0x7f55e03490b0>
Generate Columns with Area Statistics¶
Name |
Alias |
Description |
Type |
Unit |
---|---|---|---|---|
a_district |
Grunnkretsareal |
Area of the district |
FLOAT |
m2 |
a_open_space |
Åpent område |
Area of open space |
FLOAT |
m2 |
a_private_space |
Privat område |
Area of private space |
FLOAT |
m2 |
a_public_space |
Offentlig område |
Area of public space |
FLOAT |
m2 |
a_crown |
Kroneareal |
Crown coverage area within the district |
FLOAT |
m2 |
perc_crown |
Trekronedekningsgrad (%) |
Tree crown coverage % |
FLOAT |
% |
Add columns to districts table
[25]:
# update districts
# add area columns: a_open_space, a_private_space, a_public_space, a_green_space, a_crown, a_crown_public, a_crown_private
con.execute("ALTER TABLE districts ADD COLUMN a_open_space FLOAT")
con.execute("ALTER TABLE districts ADD COLUMN a_private_space FLOAT")
con.execute("ALTER TABLE districts ADD COLUMN a_public_space FLOAT")
con.execute("ALTER TABLE districts ADD COLUMN a_crown FLOAT")
con.execute("ALTER TABLE districts ADD COLUMN perc_crown FLOAT")
[25]:
<duckdb.duckdb.DuckDBPyConnection at 0x7f55e03490b0>
Caluclate AREA attributes
[26]:
# AREA of open space per district
# calculate the overlapping area of open space with district X, A etc.
con.execute(
"""
UPDATE districts
SET a_open_space = (
SELECT SUM(ST_Area(ST_Intersection(ST_GeomFromWKB(districts.geometry), split_open_space.geom)))
FROM split_open_space
WHERE ST_Intersects(ST_GeomFromWKB(districts.geometry), split_open_space.geom)
)
"""
)
# AREA of private open space per district
# calculate the overlapping area of private open space with district X, A etc.
con.execute(
"""
UPDATE districts
SET a_private_space = (
SELECT SUM(ST_Area(ST_Intersection(ST_GeomFromWKB(districts.geometry), split_private_space.geom)))
FROM split_private_space
WHERE ST_Intersects(ST_GeomFromWKB(districts.geometry), split_private_space.geom)
)
"""
)
# AREA of public open space per district
# calculate the overlapping area of public open space with district X, A etc.
con.execute(
"""
UPDATE districts
SET a_public_space = (
SELECT SUM(ST_Area(ST_Intersection(ST_GeomFromWKB(districts.geometry), split_public_space.geom)))
FROM split_public_space
WHERE ST_Intersects(ST_GeomFromWKB(districts.geometry), split_public_space.geom)
)
"""
)
# AREA of tree crowns per district
# calculate the overlapping area of tree crowns with district X, A etc.
con.execute(
"""
UPDATE districts
SET a_crown = (
SELECT SUM(ST_Area(ST_Intersection(ST_GeomFromWKB(districts.geometry), split_tree_crowns.geom)))
FROM split_tree_crowns
WHERE ST_Intersects(ST_GeomFromWKB(districts.geometry), split_tree_crowns.geom)
)
"""
)
# PERCENTAGE of tree crown coverage per district
con.execute(
"""
UPDATE districts
SET perc_crown = (a_crown / a_clipped) * 100
"""
)
[26]:
<duckdb.duckdb.DuckDBPyConnection at 0x7f55e03490b0>
Export to dataframe
[27]:
# Export districts to DataFrame
df = pd.read_sql("SELECT * FROM districts", con)
# Convert geometry column from WKB to shapely geometry
df['geometry'] = df['geometry'].apply(loads, hex=True)
# Convert DataFrame to GeoDataFrame
gdf = gpd.GeoDataFrame(df, geometry='geometry')
gdf_sorted = gdf.sort_values(by='grunnkretsnummer', ascending=True)
gdf_sorted = gdf_sorted.round(2)
# Define the %-bins and labels
labels = ["no data", "0-25%", "25-50%", "50-75%", "75-100%"]
bins = pd.IntervalIndex.from_tuples([(-0.01, 25), (25, 50), (50, 75), (75, 100)])
dict = {
"nan":"no data",
"(-0.01, 25.0]": "0-25%",
"(25.0, 50.0]": "25-50%",
"(50.0, 75.0]": "50-75%",
"(75.0, 100.0]": "75-100%"
}
# Near Residential Buildings % Categories
gdf_sorted['bldg_bins'] = pd.cut(gdf_sorted['perc_near_trees'], bins)
gdf_sorted['bldg_bins'] = gdf_sorted['bldg_bins'].astype(str)
gdf_sorted['labels_near_rbldg'] = gdf_sorted['bldg_bins'].map(dict)
# Near Green Space % Categories
gdf_sorted['gs_bins'] = pd.cut(gdf_sorted['perc_near_gs'], bins)
gdf_sorted['gs_bins'] = gdf_sorted['gs_bins'].astype(str)
gdf_sorted['labels_near_gs'] = gdf_sorted['gs_bins'].map(dict)
# Crown Coverate % Categories
gdf_sorted['crown_bins'] = pd.cut(gdf_sorted['perc_crown'], bins)
gdf_sorted['crown_bins'] = gdf_sorted['crown_bins'].astype(str)
gdf_sorted['labels_perc_crown'] = gdf_sorted['crown_bins'].map(dict)
gdf_sorted.drop(columns=['bldg_bins', 'gs_bins', 'crown_bins'], inplace=True)
# display col "kommunenavn","grunnkrestnummer", "grunnkretsnavn" n_bldg, n_res_bldg, n_res_bldg_near_gs, n_bldg_near_trees, a_open_space, a_private_space, a_public_space, a_crown, perc_crown
display(gdf_sorted[[
"kommunenavn","grunnkretsnummer", "grunnkretsnavn",
"n_trees", "n_bldg", "n_res_bldg",
"n_res_bldg_near_gs", "perc_near_gs","labels_near_gs",
"n_bldg_near_trees", "perc_near_trees", "labels_near_rbldg",
"a_open_space", "a_private_space", "a_public_space", "a_crown",
"perc_crown", "labels_perc_crown"
]]
)
gdf.crs = "EPSG:25832"
gdf_sorted.crs = "EPSG:25832"
gdf_mapping = gdf.to_crs("EPSG:4326")
kommunenavn | grunnkretsnummer | grunnkretsnavn | n_trees | n_bldg | n_res_bldg | n_res_bldg_near_gs | perc_near_gs | labels_near_gs | n_bldg_near_trees | perc_near_trees | labels_near_rbldg | a_open_space | a_private_space | a_public_space | a_crown | perc_crown | labels_perc_crown | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2 | Kristiansand | 42040701 | Grim - Dueknipen | 298 | 204 | 87 | 61 | 70.0 | 50-75% | 59.0 | 68.0 | 50-75% | 104499.24 | 32663.19 | 71273.56 | 24228.22 | 15.40 | 0-25% |
9 | Kristiansand | 42040702 | Grimsmyra | 632 | 609 | 310 | 286 | 92.0 | 75-100% | 223.0 | 72.0 | 50-75% | 161599.53 | 83491.13 | 75987.51 | 39153.62 | 15.43 | 0-25% |
15 | Kristiansand | 42040704 | Møllevannet - Klappane | 154 | 82 | 40 | 40 | 100.0 | 75-100% | 33.0 | 82.0 | 75-100% | 47883.75 | 16124.35 | 31553.15 | 9176.21 | 13.43 | 0-25% |
20 | Kristiansand | 42040705 | Enrum - Paradis | 433 | 190 | 78 | 77 | 99.0 | 75-100% | 52.0 | 67.0 | 50-75% | 58644.02 | 27538.81 | 30578.40 | 35490.59 | 32.06 | 25-50% |
1 | Kristiansand | 42040801 | Kvadraturen Sørvest | 440 | 307 | 72 | 0 | 0.0 | 0-25% | 33.0 | 46.0 | 25-50% | 205287.23 | 52343.63 | 153860.23 | 25854.21 | 5.59 | 0-25% |
5 | Kristiansand | 42040802 | Kvadraturen Nordvest | 439 | 416 | 104 | 0 | 0.0 | 0-25% | 23.0 | 22.0 | 0-25% | 194937.12 | 47966.65 | 147339.89 | 31861.02 | 9.60 | 0-25% |
3 | Kristiansand | 42040803 | Kvadraturen Sørøst | 432 | 375 | 170 | 13 | 8.0 | 0-25% | 65.0 | 38.0 | 25-50% | 190027.25 | 57009.83 | 132599.47 | 26190.16 | 4.59 | 0-25% |
6 | Kristiansand | 42040804 | Kvadraturen Nordøst | 216 | 695 | 382 | 54 | 14.0 | 0-25% | 105.0 | 27.0 | 25-50% | 145466.62 | 60967.31 | 84394.45 | 9775.37 | 3.26 | 0-25% |
10 | Kristiansand | 42040806 | Eg | 366 | 86 | 32 | 32 | 100.0 | 75-100% | 28.0 | 88.0 | 75-100% | 59291.48 | 16598.72 | 42233.00 | 23503.05 | 24.93 | 0-25% |
21 | Kristiansand | 42040807 | Sykehuset | 1291 | 99 | 1 | 1 | 100.0 | 75-100% | 1.0 | 100.0 | 75-100% | 257912.45 | 159733.95 | 97249.30 | 96850.88 | 24.05 | 0-25% |
0 | Kristiansand | 42040901 | Galgeberg | 333 | 285 | 129 | 129 | 100.0 | 75-100% | 71.0 | 55.0 | 50-75% | 118018.59 | 45730.00 | 71898.16 | 15768.14 | 6.06 | 0-25% |
7 | Kristiansand | 42040902 | Hamreheia | 481 | 564 | 250 | 248 | 99.0 | 75-100% | 178.0 | 71.0 | 50-75% | 173022.23 | 90216.37 | 81218.94 | 23997.30 | 8.18 | 0-25% |
4 | Kristiansand | 42040903 | Kuholmen | 332 | 395 | 160 | 160 | 100.0 | 75-100% | 94.0 | 59.0 | 50-75% | 188112.45 | 64551.17 | 122261.60 | 16465.98 | 5.34 | 0-25% |
12 | Kristiansand | 42040904 | Valhalla Sør | 128 | 241 | 124 | 114 | 92.0 | 75-100% | 67.0 | 54.0 | 50-75% | 62455.76 | 34136.45 | 27904.97 | 4095.61 | 4.88 | 0-25% |
13 | Kristiansand | 42040905 | Valhalla Midt | 276 | 499 | 261 | 82 | 31.0 | 25-50% | 134.0 | 51.0 | 50-75% | 124027.37 | 64567.89 | 58371.44 | 12991.49 | 7.35 | 0-25% |
25 | Kristiansand | 42040907 | Valhalla Nord - Kongsgård | 439 | 303 | 155 | 155 | 100.0 | 75-100% | 124.0 | 80.0 | 75-100% | 148043.27 | 68896.92 | 77977.48 | 25669.20 | 12.04 | 0-25% |
17 | Kristiansand | 42040908 | Solbygg | 122 | 98 | 50 | 50 | 100.0 | 75-100% | 29.0 | 58.0 | 50-75% | 73094.88 | 29007.49 | 43483.53 | 6955.10 | 6.78 | 0-25% |
23 | Kristiansand | 42040909 | Kjempegravane | 47 | 132 | 61 | 61 | 100.0 | 75-100% | 18.0 | 30.0 | 25-50% | 34834.33 | 18410.68 | 15887.13 | 2604.27 | 5.22 | 0-25% |
28 | Kristiansand | 42040910 | Tobienborg | 381 | 302 | 148 | 148 | 100.0 | 75-100% | 72.0 | 49.0 | 25-50% | 131510.67 | 50772.92 | 79382.28 | 23394.14 | 12.40 | 0-25% |
11 | Kristiansand | 42040911 | Nedre Lund | 402 | 526 | 187 | 177 | 95.0 | 75-100% | 121.0 | 65.0 | 50-75% | 205347.41 | 89383.95 | 114498.01 | 21780.21 | 6.46 | 0-25% |
24 | Kristiansand | 42040912 | Oddemarka | 118 | 108 | 31 | 31 | 100.0 | 75-100% | 15.0 | 48.0 | 25-50% | 54084.15 | 21627.48 | 32029.34 | 7577.51 | 10.27 | 0-25% |
22 | Kristiansand | 42040913 | Flaten | 103 | 162 | 80 | 80 | 100.0 | 75-100% | 52.0 | 65.0 | 50-75% | 45788.73 | 23253.30 | 22286.44 | 5368.99 | 8.09 | 0-25% |
29 | Kristiansand | 42040915 | Gimlemoen - Jegersberg | 580 | 40 | 1 | 1 | 100.0 | 75-100% | 1.0 | 100.0 | 75-100% | 145747.75 | 31836.04 | 113747.86 | 45532.23 | 21.19 | 0-25% |
16 | Kristiansand | 42040918 | Gimlevang | 34 | 42 | 19 | 19 | 100.0 | 75-100% | 8.0 | 42.0 | 25-50% | 28194.61 | 10814.88 | 17202.92 | 1886.51 | 4.09 | 0-25% |
27 | Kristiansand | 42040919 | Gimle | 1175 | 21 | 2 | 2 | 100.0 | 75-100% | 1.0 | 50.0 | 25-50% | 87002.41 | 15415.42 | 71380.45 | 89392.76 | 48.56 | 25-50% |
8 | Kristiansand | 42040920 | Marviksletta | 48 | 6 | 0 | 0 | NaN | no data | NaN | NaN | no data | 12271.83 | 6491.21 | 5721.18 | 3426.57 | 18.12 | 0-25% |
14 | Kristiansand | 42040921 | Lund Industriområde | 40 | 9 | 0 | 0 | NaN | no data | NaN | NaN | no data | 16471.13 | 11996.89 | 4519.13 | 2207.87 | 8.89 | 0-25% |
18 | Kristiansand | 42041101 | Nedre Kongsgård | 1008 | 390 | 29 | 29 | 100.0 | 75-100% | 21.0 | 72.0 | 50-75% | 218743.47 | 87159.60 | 130853.74 | 57912.16 | 18.42 | 0-25% |
19 | Kristiansand | 42041102 | Kongsgard 1 - Vige | 166 | 9 | 0 | 0 | NaN | no data | NaN | NaN | no data | 9656.72 | 4293.83 | 5215.83 | 11158.61 | 49.82 | 25-50% |
26 | Kristiansand | 42041103 | Bjørndalsheia | 129 | 38 | 12 | 12 | 100.0 | 75-100% | 12.0 | 100.0 | 75-100% | 40086.45 | 5665.55 | 34368.30 | 8886.66 | 17.37 | 0-25% |
30 | Kristiansand | 42041104 | Vestre Bjørndalen | 36 | 20 | 2 | 2 | 100.0 | 75-100% | 1.0 | 50.0 | 25-50% | 45113.70 | 11336.80 | 33673.90 | 1164.64 | 2.24 | 0-25% |
[28]:
%%capture
print(gdf_sorted.columns)
#drop duckdb geom col
gdf_sorted.drop(columns=['st_geomfromwkb(geometry)'], inplace=True)
# sort columns
new_order = ['OBJECTID', 'fylkesnummer', 'fylkesnavn', 'kommunenummer',
'kommunenavn', 'delomradenummer', 'delomradenavn', 'grunnkretsnummer',
'grunnkretsnavn', 'kilde_admin', 'kilde_befolkning', 'id_befolkning',
'year_pop_stat', 'pop_total', 'pop_elderly', 'a_district', 'a_unit',
'a_clipped', 'n_trees', 'n_bldg', 'n_res_bldg',
'n_res_bldg_near_gs', 'perc_near_gs', 'labels_near_gs',
'n_bldg_near_trees', 'perc_near_trees', 'labels_near_rbldg',
'a_open_space', 'a_private_space', 'a_public_space',
'a_crown', 'perc_crown','labels_perc_crown',
'SHAPE_Length', 'SHAPE_Area', 'geometry']
# Reorder the columns
gdf_sorted = gdf_sorted[new_order]
print(gdf_sorted.columns)
Export to Parquet, GeoJSON and CSV format
[29]:
# Export GDF to file
filepath = os.path.join(reporting_dir, f"{municipality}_district_treeVis_stat")
# Write to .parquet
gdf_sorted.to_parquet(os.path.join(filepath + '.parquet'))
# Write to .geojson
gdf_sorted.to_file(os.path.join(filepath + '.geojson'), driver='GeoJSON')
# Write to .csv
gdf_sorted.to_csv(os.path.join(filepath + '.csv'))