Access S2S via Database

Access S2S via Database#

from typing import Dict

import folium as flm  # Comment out if you have not installed, or run pip install folium matplotlib mapclassify
import geopandas as gpd
import numpy as np
import pandas as pd
from dotenv import load_dotenv
from geojson_pydantic import Feature, Polygon
from lonboard import Map, ScatterplotLayer
from shapely import from_geojson
from space2stats import StatsTable

Expected environment variables to be set from dotenv file

PGHOST=
PGPORT=
PGDATABASE=
PGUSER=
PGPASSWORD=
PGTABLENAME=space2stats
load_dotenv("../../db.env")
True

Connect to the database and print available fields

with StatsTable.connect() as stats_table:
    fields = stats_table.fields()

fields
['pop_flood_pct',
 'ogc_fid',
 'sum_pop_f_0_2020',
 'sum_pop_f_10_2020',
 'sum_pop_f_15_2020',
 'sum_pop_f_1_2020',
 'sum_pop_f_20_2020',
 'sum_pop_f_25_2020',
 'sum_pop_f_30_2020',
 'sum_pop_f_35_2020',
 'sum_pop_f_40_2020',
 'sum_pop_f_45_2020',
 'sum_pop_f_50_2020',
 'sum_pop_f_55_2020',
 'sum_pop_f_5_2020',
 'sum_pop_f_60_2020',
 'sum_pop_f_65_2020',
 'sum_pop_f_70_2020',
 'sum_pop_f_75_2020',
 'sum_pop_f_80_2020',
 'sum_pop_m_0_2020',
 'sum_pop_m_10_2020',
 'sum_pop_m_15_2020',
 'sum_pop_m_1_2020',
 'sum_pop_m_20_2020',
 'sum_pop_m_25_2020',
 'sum_pop_m_30_2020',
 'sum_pop_m_35_2020',
 'sum_pop_m_40_2020',
 'sum_pop_m_45_2020',
 'sum_pop_m_50_2020',
 'sum_pop_m_55_2020',
 'sum_pop_m_5_2020',
 'sum_pop_m_60_2020',
 'sum_pop_m_65_2020',
 'sum_pop_m_70_2020',
 'sum_pop_m_75_2020',
 'sum_pop_m_80_2020',
 'sum_pop_m_2020',
 'sum_pop_f_2020',
 'sum_pop_2020',
 'pop',
 'pop_flood']

Define a geometry, in this case we use a bounding box surrounding Kenya and verify the GeoJSON Feature

AOIModel = Feature[Polygon, Dict]

# ~burundi
minx, miny, maxx, maxy = 29.038924, -4.468958, 30.850461, -2.310523

aoi = {
    "type": "Feature",
    "geometry": {
        "type": "Polygon",
        "coordinates": [
            [
                [minx, maxy],
                [minx, miny],
                [maxx, miny],
                [maxx, maxy],
                [minx, maxy],
            ]
        ],
    },
    "properties": {"name": "Updated AOI"},
}


feat = AOIModel(**aoi)

Connect to the database and retrieve hexagons that intersect with the bounding box

with StatsTable.connect() as stats_table:
    data = stats_table.summaries(
        aoi=feat,
        spatial_join_method="touches",
        fields=["sum_pop_2020"],
        geometry="point",
    )
    df = pd.DataFrame(data)

df.head()
hex_id geometry sum_pop_2020
0 866ad8087ffffff {"type":"Point","coordinates":[30.081627708207... 25345.874701
1 866ad808fffffff {"type":"Point","coordinates":[30.139814038919... 11870.360712
2 866ad8097ffffff {"type":"Point","coordinates":[30.032345934224... 19034.332476
3 866ad809fffffff {"type":"Point","coordinates":[30.090538449149... 14700.551092
4 866ad80c7ffffff {"type":"Point","coordinates":[30.247261402176... 12067.935215

Retrieve all fields and polygon geometries for hexagons

with StatsTable.connect() as stats_table:
    data = stats_table.summaries(
        aoi=feat, spatial_join_method="touches", fields=fields, geometry="polygon"
    )
    df = pd.DataFrame(data)

df.head()
hex_id geometry pop_flood_pct ogc_fid sum_pop_f_0_2020 sum_pop_f_10_2020 sum_pop_f_15_2020 sum_pop_f_1_2020 sum_pop_f_20_2020 sum_pop_f_25_2020 ... sum_pop_m_60_2020 sum_pop_m_65_2020 sum_pop_m_70_2020 sum_pop_m_75_2020 sum_pop_m_80_2020 sum_pop_m_2020 sum_pop_f_2020 sum_pop_2020 pop pop_flood
0 866ad8087ffffff {"type":"Polygon","coordinates":[[[30.10399360... 0.050492 6194996 318.054565 1747.088623 1923.321899 1251.022461 990.854004 1019.086853 ... 206.265076 122.173080 48.124065 19.658331 24.460270 12000.383184 13345.491516 25345.874701 26163.988 1321.075323
1 866ad808fffffff {"type":"Polygon","coordinates":[[[30.16218780... 0.107920 6194997 146.099442 825.470764 822.941284 574.661377 440.004395 454.970947 ... 122.356934 70.530174 25.380550 14.198433 14.668016 5801.703337 6068.657375 11870.360712 12280.807 1325.350181
2 866ad8097ffffff {"type":"Polygon","coordinates":[[[30.05471035... 0.039941 6194998 276.175537 1284.143066 1353.945312 1069.982666 753.500305 765.620483 ... 140.353241 89.866302 38.299133 19.963312 19.103374 9014.848974 10019.483501 19034.332476 18063.918 721.491072
3 866ad809fffffff {"type":"Polygon","coordinates":[[[30.11291078... 0.035242 6194999 190.571701 1002.784302 1023.486633 749.586670 556.957886 575.871033 ... 142.405670 81.206604 30.017090 15.441221 16.896353 7089.448963 7611.102129 14700.551092 14131.070 498.011870
4 866ad80c7ffffff {"type":"Polygon","coordinates":[[[30.26964435... 0.069827 6195003 169.925201 832.059998 751.781555 668.376648 389.565125 483.499878 ... 82.641251 48.474693 22.666998 11.139693 12.547176 5887.773416 6180.161799 12067.935215 12149.723 848.372900

5 rows × 45 columns

Convert to geopandas geodataframe

df["geometry"] = df["geometry"].apply(lambda geom: from_geojson(geom))
gdf = gpd.GeoDataFrame(df, geometry="geometry", crs="EPSG:4326")
gdf.head()
hex_id geometry pop_flood_pct ogc_fid sum_pop_f_0_2020 sum_pop_f_10_2020 sum_pop_f_15_2020 sum_pop_f_1_2020 sum_pop_f_20_2020 sum_pop_f_25_2020 ... sum_pop_m_60_2020 sum_pop_m_65_2020 sum_pop_m_70_2020 sum_pop_m_75_2020 sum_pop_m_80_2020 sum_pop_m_2020 sum_pop_f_2020 sum_pop_2020 pop pop_flood
0 866ad8087ffffff POLYGON ((30.10399 -2.3257, 30.11745 -2.29278,... 0.050492 6194996 318.054565 1747.088623 1923.321899 1251.022461 990.854004 1019.086853 ... 206.265076 122.173080 48.124065 19.658331 24.460270 12000.383184 13345.491516 25345.874701 26163.988 1321.075323
1 866ad808fffffff POLYGON ((30.16219 -2.34876, 30.17563 -2.31584... 0.107920 6194997 146.099442 825.470764 822.941284 574.661377 440.004395 454.970947 ... 122.356934 70.530174 25.380550 14.198433 14.668016 5801.703337 6068.657375 11870.360712 12280.807 1325.350181
2 866ad8097ffffff POLYGON ((30.05471 -2.36355, 30.06817 -2.33062... 0.039941 6194998 276.175537 1284.143066 1353.945312 1069.982666 753.500305 765.620483 ... 140.353241 89.866302 38.299133 19.963312 19.103374 9014.848974 10019.483501 19034.332476 18063.918 721.491072
3 866ad809fffffff POLYGON ((30.11291 -2.38662, 30.12636 -2.35369... 0.035242 6194999 190.571701 1002.784302 1023.486633 749.586670 556.957886 575.871033 ... 142.405670 81.206604 30.017090 15.441221 16.896353 7089.448963 7611.102129 14700.551092 14131.070 498.011870
4 866ad80c7ffffff POLYGON ((30.26964 -2.33398, 30.28307 -2.30106... 0.069827 6195003 169.925201 832.059998 751.781555 668.376648 389.565125 483.499878 ... 82.641251 48.474693 22.666998 11.139693 12.547176 5887.773416 6180.161799 12067.935215 12149.723 848.372900

5 rows × 45 columns

m = gdf.explore(
    column="sum_pop_2020",
    tooltip="sum_pop_2020",
    cmap="YlGnBu",
    legend=True,
    scheme="naturalbreaks",
    legend_kwds=dict(colorbar=True, caption="Population", interval=False),
    style_kwds=dict(weight=0, fillOpacity=0.8),
    name="Population by Hexagon",
)
flm.LayerControl("topright", collapsed=False).add_to(m)
m
Make this Notebook Trusted to load map: File -> Trust Notebook

A faster interactive map can be created using lonboard

gdf.loc[:, "geometry"] = gdf.geometry.representative_point()
# Define custom breaks and corresponding RGBA colors
breaks = [
    gdf["sum_pop_2020"].min(),
    1,
    1000,
    10000,
    50000,
    100000,
    200000,
    gdf["sum_pop_2020"].max(),
]
colors = np.array(
    [
        [211, 211, 211, 255],  # Light gray for 0
        [255, 255, 0, 255],  # Yellow for 1-1000
        [255, 165, 0, 255],  # Orange for 1000-10000
        [255, 0, 0, 255],  # Red for 10000-50000
        [128, 0, 128, 255],  # Purple for 50000-100000
        [0, 0, 255, 255],  # Blue for 100000-200000
        [0, 0, 139, 255],  # Dark blue for 200000+
    ]
)

# Function to assign colors based on custom bins


def assign_color(value, breaks, colors):
    for i in range(len(breaks) - 1):
        if breaks[i] <= value < breaks[i + 1]:
            return colors[i]
    return colors[-1]  # In case value exceeds all breaks


# Map sum_pop_2020 values to colors using the custom function
gdf["color"] = gdf["sum_pop_2020"].apply(lambda x: assign_color(x, breaks, colors))
colors = np.uint8(gdf["color"].tolist())

# Create the scatterplot layer with the assigned colors
layer = ScatterplotLayer.from_geopandas(gdf, get_radius=2000, get_fill_color=colors)

m = Map(layer)
m