diff --git a/.ghsci_version b/.ghsci_version index 4e298cc9..6016e8ad 100644 --- a/.ghsci_version +++ b/.ghsci_version @@ -1 +1 @@ -4.5.3 +4.6.0 diff --git a/.test-compose.yml b/.test-compose.yml index 170b75e1..d4adca11 100644 --- a/.test-compose.yml +++ b/.test-compose.yml @@ -1,7 +1,6 @@ -version: "3" services: ghsci: - image: globalhealthyliveablecities/global-indicators:v4.5.1 + image: globalhealthyliveablecities/global-indicators:v4.6.0 container_name: ghsci shm_size: 2g stdin_open: true # docker run -i diff --git a/docker-compose.yml b/docker-compose.yml index eb439c9c..e1716f30 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: ghsci: - image: globalhealthyliveablecities/global-indicators:v4.5.2 + image: globalhealthyliveablecities/global-indicators:v4.6.0 container_name: ghsci shm_size: 2g stdin_open: true # docker run -i diff --git a/docker/Dockerfile b/docker/Dockerfile index f8339a0e..fc7e2eea 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -18,7 +18,7 @@ # >>> docker rmi $(docker images -q) --force ############################################################################## -FROM continuumio/miniconda3:latest as build +FROM continuumio/miniconda3:latest AS build LABEL maintainer="Global Healthy Liveable City Indicator Study Collaboration Group" LABEL url="https://github.com/global-healthy-liveable-cities/global-indicators" @@ -39,7 +39,7 @@ RUN conda config --set show_channel_urls true && \ # ============================================================================= # Runtime environment -FROM debian:stable-slim as runtime +FROM debian:stable-slim AS runtime # Install the environment pack COPY --from=build /env.tar.gz . diff --git a/docker/environment.yml b/docker/environment.yml index 67ea704e..10ad0926 100644 --- a/docker/environment.yml +++ b/docker/environment.yml @@ -3,22 +3,19 @@ channels: - conda-forge dependencies: - python - - osmnx=1.5.* # OpenStreetMap network retrieval and analysis + - osmnx=1.9.3 # OpenStreetMap network retrieval and analysis + - geopandas=1.0.* - pandas=2.1.* - fpdf2=2.7.* # pdf templating - sqlalchemy=1.4.* # SQL connectivity with PostgreSQL - - nicegui=1.4.26 # web app framework + - nicegui=1.4.29 # web app framework - cryptography=42.0.*,>=42.0.4 - - requests=2.31.* - - tornado>=6.3.* + - tornado=6.4.*,>=6.4.1 - fonttools=4.43.* - - jupyterlab=4.0.* # code notebooks / web interface - - jupyter-lsp=2.2.* - - urllib3=2.0.* + - jupyterlab=4.2.* # code notebooks / web interface - pillow=10.*,>=10.3.0 - - pip>=24.* + - pip=24.* - pyrosm=0.6.* # possible alternate OSM/network retrieval - - orjson=3.9.*,>=3.9.15 - idna>=3.7 - tqdm=4.*, >=4.66.3 - openpyxl @@ -33,3 +30,4 @@ dependencies: - psycopg2 # PostgreSQL connection - pyyaml # YAML parsing - websockets + - zipp=3.19.*, >=3.19.1 # https://github.com/healthysustainablecities/global-indicators/security/dependabot/61 diff --git a/docker/requirements.txt b/docker/requirements.txt index a3072bdd..0906ad81 100644 --- a/docker/requirements.txt +++ b/docker/requirements.txt @@ -1,5 +1,5 @@ affine==2.4.0 -aiofiles==23.2.1 +aiofiles==24.1.0 aiohttp==3.9.5 aiosignal==1.3.1 annotated-types==0.7.0 @@ -18,7 +18,7 @@ branca==0.7.2 Brotli==1.1.0 cached-property==1.5.2 Cartopy==0.23.0 -certifi==2024.2.2 +certifi==2024.7.4 cffi==1.16.0 charset-normalizer==3.3.2 click==8.1.7 @@ -28,34 +28,33 @@ colorama==0.4.6 comm==0.2.2 contextily==1.6.0 contourpy==1.2.1 -cryptography==42.0.7 +cryptography==42.0.8 cycler==0.12.1 cykhash==2.0.1 Cython==3.0.10 dataclasses==0.8 -debugpy==1.8.1 +debugpy==1.8.2 decorator==5.1.1 defusedxml==0.7.1 docutils==0.21.2 entrypoints==0.4 et-xmlfile==1.1.0 -exceptiongroup==1.2.0 +exceptiongroup==1.2.2 executing==2.0.1 fastapi==0.109.2 -fastjsonschema==2.19.1 -fiona==1.9.6 -folium==0.16.0 +fastjsonschema==2.20.0 +folium==0.17.0 fonttools==4.43.1 fpdf2==2.7.9 fqdn==1.5.1 frozenlist==1.4.1 -GDAL==3.8.5 -GeoAlchemy2==0.14.7 +GDAL==3.9.1 +GeoAlchemy2==0.15.2 geographiclib==2.0 -geopandas==0.14.4 +geopandas==1.0.1 geopy==2.4.1 greenlet==3.0.3 -gtfs-lite==0.2.1 +gtfs-lite==0.2.2 h11==0.14.0 h2==4.1.0 hpack==4.0.0 @@ -64,10 +63,10 @@ httpx==0.27.0 hyperframe==6.0.1 idna==3.7 ifaddr==0.2.0 -importlib_metadata==7.1.0 +importlib_metadata==8.0.0 importlib_resources==6.4.0 -ipykernel==6.29.3 -ipython==8.25.0 +ipykernel==6.29.5 +ipython==8.26.0 ipywidgets==8.1.3 isoduration==20.11.0 itsdangerous==2.2.0 @@ -75,25 +74,25 @@ jedi==0.19.1 Jinja2==3.1.4 joblib==1.4.2 json5==0.9.25 -jsonpointer==2.4 -jsonschema==4.22.0 +jsonpointer==3.0.0 +jsonschema==4.23.0 jsonschema-specifications==2023.12.1 jupyter_client==8.6.2 jupyter_core==5.7.2 jupyter-events==0.10.0 jupyter-lsp==2.2.5 -jupyter_server==2.14.1 +jupyter_server==2.14.2 jupyter_server_terminals==0.5.3 -jupyterlab==4.0.13 +jupyterlab==4.2.3 jupyterlab_pygments==0.3.0 -jupyterlab_server==2.27.2 +jupyterlab_server==2.27.3 jupyterlab_widgets==3.0.11 kiwisolver==1.4.5 lxml==5.2.2 mapclassify==2.6.1 -markdown2==2.4.13 +markdown2==2.5.0 MarkupSafe==2.1.5 -matplotlib==3.8.4 +matplotlib==3.9.1 matplotlib-inline==0.1.7 mercantile==1.2.1 mistune==3.0.2 @@ -104,39 +103,40 @@ nbconvert==7.16.4 nbformat==5.10.4 nest_asyncio==1.6.0 networkx==3.3 -nicegui==1.4.25.dev0 +nicegui==1.4.28.dev0 notebook_shim==0.2.4 numexpr==2.10.0 numpy==1.26.4 -openpyxl==3.1.2 -orjson==3.9.15 -osmnx==1.5.1 +openpyxl==3.1.4 +orjson==3.10.6 +osmnx==1.9.3 overrides==7.7.0 OWSLib==0.31.0 -packaging==24.0 +packaging==24.1 pandana==0.7 pandas==2.1.4 pandocfilters==1.5.0 parso==0.8.4 pexpect==4.9.0 pickleshare==0.7.5 -pillow==10.3.0 +pillow==10.4.0 pip==24.0 pkgutil_resolve_name==1.3.10 platformdirs==4.2.2 prometheus_client==0.20.0 -prompt-toolkit==3.0.42 +prompt_toolkit==3.0.47 pscript==0.7.7 -psutil==5.9.8 +psutil==6.0.0 psycopg2==2.9.9 ptyprocess==0.7.0 pure-eval==0.2.2 py-cpuinfo==9.0.0 pycparser==2.22 -pydantic==2.7.2 -pydantic_core==2.18.3 +pydantic==2.8.2 +pydantic_core==2.20.1 pygeometa==0.15.3 Pygments==2.18.0 +pyogrio==0.9.0 pyparsing==3.1.2 pyproj==3.6.1 pyrobuf==0.9.3 @@ -147,23 +147,22 @@ python-dateutil==2.9.0 python-engineio==4.9.1 python-json-logger==2.0.7 python-multipart==0.0.9 -python-rapidjson==1.17 -python-socketio==5.11.2 +python-rapidjson==1.18 +python-socketio==5.11.3 pytz==2024.1 PyYAML==6.0.1 pyzmq==26.0.3 rasterio==1.3.10 referencing==0.35.1 -requests==2.31.0 +requests==2.32.3 rfc3339-validator==0.1.4 rfc3986-validator==0.1.1 -rpds-py==0.18.1 -Rtree==1.2.0 -scikit-learn==1.5.0 -scipy==1.13.1 +rpds-py==0.19.0 +scikit-learn==1.5.1 +scipy==1.14.0 Send2Trash==1.8.3 -setuptools==70.0.0 -shapely==2.0.4 +setuptools==71.0.1 +shapely==2.0.5 simple-websocket==1.0.0 six==1.16.0 sniffio==1.3.1 @@ -178,27 +177,28 @@ terminado==0.18.1 threadpoolctl==3.5.0 tinycss2==1.3.0 tomli==2.0.1 -tornado==6.4 +tornado==6.4.1 tqdm==4.66.4 traitlets==5.14.3 types-python-dateutil==2.9.0.20240316 -typing_extensions==4.12.1 +typing_extensions==4.12.2 typing-utils==0.1.0 tzdata==2024.1 -uharfbuzz==0.39.1 +uharfbuzz==0.39.3 uri-template==1.3.0 -urllib3==2.0.7 -uvicorn==0.30.0 +urllib3==2.2.2 +uvicorn==0.30.1 vbuild==0.8.2 watchfiles==0.22.0 wcwidth==0.2.13 -webcolors==1.13 +webcolors==24.6.0 webencodings==0.5.1 websocket-client==1.8.0 websockets==12.0 wheel==0.43.0 widgetsnbextension==4.0.11 wsproto==1.2.0 -xyzservices==2024.4.0 +xyzservices==2024.6.0 yarl==1.9.4 -zipp==3.17.0 +zipp==3.19.2 +zstandard==0.23.0 diff --git a/process/analysis.py b/process/analysis.py index a6a8bd0e..d209014a 100644 --- a/process/analysis.py +++ b/process/analysis.py @@ -161,6 +161,7 @@ def analysis(r): "When using psql, you can type '\\dt' to list database tables, '\\d ' to list table columns, and 'SELECT * FROM LIMIT 10;' to view the first 10 rows of a table. To exit psql, enter '\\q'." '\n\n', ) + append_to_log_file.close() def main(): diff --git a/process/configuration/templates/_report_configuration.xlsx b/process/configuration/templates/_report_configuration.xlsx index 3aa3e1d1..7284925b 100644 Binary files a/process/configuration/templates/_report_configuration.xlsx and b/process/configuration/templates/_report_configuration.xlsx differ diff --git a/process/example.ipynb b/process/example.ipynb index 75b8d8f3..213c5418 100644 --- a/process/example.ipynb +++ b/process/example.ipynb @@ -424,7 +424,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.12.4" } }, "nbformat": 4, diff --git a/process/subprocesses/bulk_gdal_merge.py b/process/subprocesses/bulk_gdal_merge.py index 36ac5f0b..76159a6f 100644 --- a/process/subprocesses/bulk_gdal_merge.py +++ b/process/subprocesses/bulk_gdal_merge.py @@ -24,10 +24,16 @@ def valid_path(arg): description='Generate origin destination matrix', ) parser.add_argument( - '-dir', help='parent directory', default=cwd, type=valid_path, + '-dir', + help='parent directory', + default=cwd, + type=valid_path, ) parser.add_argument( - '-outfile', help='outfile name', default='gdal_merged.tif', type=str, + '-outfile', + help='outfile name', + default='gdal_merged.tif', + type=str, ) parser.add_argument( '-gdal_loc', @@ -39,7 +45,7 @@ def valid_path(arg): # initialise tif list file tif_list_name = 'tif_list_{date:%Y-%m-%d}.txt'.format( - date=datetime.datetime.now(), + date=datetime.datetime.now(datetime.UTC), ) tif_list_path = os.path.join(args.dir, tif_list_name) tif_list = open(tif_list_path, 'w') diff --git a/process/subprocesses/setup_sp.py b/process/subprocesses/setup_sp.py index 23339517..099485a5 100644 --- a/process/subprocesses/setup_sp.py +++ b/process/subprocesses/setup_sp.py @@ -17,7 +17,10 @@ def spatial_join_index_to_gdf( - gdf, join_gdf, join_type='within', dropna=True, + gdf, + join_gdf, + join_type='within', + dropna=True, ): """Append to a geodataframe the named index of another using spatial join. @@ -34,8 +37,7 @@ def spatial_join_index_to_gdf( """ gdf_columns = list(gdf.columns) gdf = gpd.sjoin(gdf, join_gdf, how='left', predicate=join_type) - gdf = gdf[gdf_columns + ['index_right']] - gdf.columns = gdf_columns + [join_gdf.index.name] + gdf = gdf[gdf_columns + [join_gdf.index.name]] if dropna: gdf = gdf[~gdf[join_gdf.index.name].isna()] gdf[join_gdf.index.name] = gdf[join_gdf.index.name].astype( @@ -238,7 +240,11 @@ def cal_dist_node_to_nearest_pois( output_names = [f'{output_prefix}{x}' for x in output_names] network.set_pois( - output_names[0], distance, 1, gdf_poi['x'], gdf_poi['y'], + output_names[0], + distance, + 1, + gdf_poi['x'], + gdf_poi['y'], ) gdf_poi_dist = network.nearest_pois(distance, output_names[0], 1, -999) # change the index name to match desired or default output @@ -249,7 +255,10 @@ def cal_dist_node_to_nearest_pois( def create_full_nodes( - samplePointsData, gdf_nodes_simple, gdf_nodes_poi_dist, density_statistics, + samplePointsData, + gdf_nodes_simple, + gdf_nodes_poi_dist, + density_statistics, ): """Create long form working dataset of sample points to evaluate respective node distances and densities. @@ -281,7 +290,8 @@ def create_full_nodes( pd.concat( [ samplePointsData.query('n1_distance==0')[['n1']].rename( - {'n1': 'node'}, axis='columns', + {'n1': 'node'}, + axis='columns', ), samplePointsData.query('n1_distance!=0 and n2_distance==0')[ ['n2'] @@ -308,7 +318,10 @@ def create_full_nodes( def process_distant_nodes( - samplePointsData, gdf_nodes_simple, gdf_nodes_poi_dist, density_statistics, + samplePointsData, + gdf_nodes_simple, + gdf_nodes_poi_dist, + density_statistics, ): """Create long form working dataset of sample points to evaluate respective node distances and densities. @@ -340,10 +353,13 @@ def process_distant_nodes( ) distant_nodes = distant_nodes[['nodes']].explode('nodes') distant_nodes[['node', 'node_distance_m']] = pd.DataFrame( - distant_nodes.nodes.values.tolist(), index=distant_nodes.index, + distant_nodes.nodes.values.tolist(), + index=distant_nodes.index, ) distant_nodes = distant_nodes[['node', 'node_distance_m']].join( - gdf_nodes_poi_dist, on='node', how='left', + gdf_nodes_poi_dist, + on='node', + how='left', ) distance_fields = [] for d in list(gdf_nodes_poi_dist.columns): @@ -384,7 +400,9 @@ def process_distant_nodes( ) # join up full nodes with density fields distant_nodes = distant_nodes.join( - gdf_nodes_simple[density_statistics], on='node', how='left', + gdf_nodes_simple[density_statistics], + on='node', + how='left', ) for statistic in density_statistics: distant_nodes[statistic] = ( @@ -466,7 +484,10 @@ def soft_access_score(df, distance_names, threshold=500, k=5): # Reference: Vale, D. S., & Pereira, M. (2017). # The influence of the impedance function on gravity-based pedestrian accessibility measures def cumulative_gaussian_access_score( - df, distance_names, threshold=500, k=129842, + df, + distance_names, + threshold=500, + k=129842, ): """Calculate accessibiity score using Cumulative-Gaussian approach. diff --git a/process/subprocesses/sketch_config_gui.py b/process/subprocesses/sketch_config_gui.py new file mode 100644 index 00000000..7ebdba11 --- /dev/null +++ b/process/subprocesses/sketch_config_gui.py @@ -0,0 +1,142 @@ +"""Sketch a configuration GUI for a study region.""" + +from pathlib import Path + +import yaml +from nicegui import ui + +template = Path('/home/ghsci/process/configuration/assets/region_template.yml') + + +with ui.stepper().props('vertical').classes('w-full') as stepper: + with open(template) as f: + config = yaml.safe_load(f) + # for key, value in config.items(): + with ui.step('Study region details'): + # with ui.expansion(text='Expand to view and edit', group='group') + ui.input( + label='Full study region name', + placeholder='Las Palmas de Gran Canaria', + # validation={'Input too long': lambda value: len(value) < 50}, + on_change=lambda: preview_config.refresh(), + ).bind_value_to(config, 'name').style('min-width:500px;') + ui.number( + label='Target year for analysis', + format='%4.0g', + placeholder=2023, + min=0, + max=2100, + precision=0, + on_change=lambda: preview_config.refresh(), + ).bind_value_to(config, 'year').style('min-width:300px;') + ui.input( + label='Fully country name', + placeholder='España', + on_change=lambda: preview_config.refresh(), + ).bind_value_to(config, 'name').style('min-width:500px;') + ui.input( + label='Two character country code (ISO3166 Alpha-2 code)', + placeholder='ES', + on_change=lambda: preview_config.refresh(), + ).bind_value_to(config, 'name').style('min-width:500px;') + for key, value in config['crs'].items(): + ui.input( + label=key, + placeholder=value, + on_change=lambda: preview_config.refresh(), + ).bind_value_to(config['crs'], key) + with ui.stepper_navigation(): + ui.button('Next', on_click=stepper.next) + with ui.step('Study region boundary data'): + for key, value in config['study_region_boundary'].items(): + ui.input( + label=key, + placeholder=value, + on_change=lambda: preview_config.refresh(), + ).bind_value_to(config['study_region_boundary'], key) + with ui.stepper_navigation(): + ui.button('Back', on_click=stepper.previous).props('flat') + ui.button('Next', on_click=stepper.next) + # with ui.step('Custom aggregation'): + # for key, value in config['custom_aggregations'].items(): + # ui.input( + # label=key, + # placeholder=value, + # on_change=lambda: preview_config.refresh() + # ).bind_value_to(config['custom_aggregations'], key) + # with ui.stepper_navigation(): + # ui.button('Back', on_click=stepper.previous).props('flat') + # ui.button('Next', on_click=stepper.next) + with ui.step('Population data'): + for key, value in config['population'].items(): + ui.input( + label=key, + placeholder=value, + on_change=lambda: preview_config.refresh(), + ).bind_value_to(config['population'], key) + with ui.stepper_navigation(): + ui.button('Back', on_click=stepper.previous).props('flat') + ui.button('Next', on_click=stepper.next) + with ui.step('OpenStreetMap data'): + for key, value in config['OpenStreetMap'].items(): + ui.input( + label=key, + placeholder=value, + on_change=lambda: preview_config.refresh(), + ).bind_value_to(config['OpenStreetMap'], key) + with ui.stepper_navigation(): + ui.button('Back', on_click=stepper.previous).props('flat') + ui.button('Next', on_click=stepper.next) + with ui.step('Pedestrian street network data'): + for key, value in config['network'].items(): + ui.input( + label=key, + placeholder=value, + on_change=lambda: preview_config.refresh(), + ).bind_value_to(config['network'], key) + with ui.stepper_navigation(): + ui.button('Back', on_click=stepper.previous).props('flat') + ui.button('Next', on_click=stepper.next) + with ui.step('Urban region data'): + for key, value in config['urban_region'].items(): + ui.input( + label=key, + placeholder=value, + on_change=lambda: preview_config.refresh(), + ).bind_value_to(config['urban_region'], key) + # Query used to identify the specific urban region relevant for this region in the Urban Centres database + ## GHS or other linkage of covariate data (GHS:variable='value', or path:variable='value' for a dataset with equivalently named fields defined in project parameters for air_pollution_covariates), e.g. GHS:UC_NM_MN=='Las Palmas de Gran Canaria' and CTR_MN_NM=='Spain' + ui.input( + 'Urban query', + placeholder="GHS:UC_NM_MN=='Las Palmas de Gran Canaria' and CTR_MN_NM=='Spain'", + ).bind_value_to(config['urban_region'], 'urban_query') + # Additional study region summary covariates to be optionally linked. This is designed to retrieve the list of covariates specifies in the 'urban_region' configuration, either from the configured Global Human Settlements Layer data (enter "urban_query"), or from a CSV file (provide a path relative to the project data directory) + ui.input( + 'Covariate data', + placeholder='Urban query', + ).bind_value_to(config['urban_region'], 'urban_query') + with ui.stepper_navigation(): + ui.button('Back', on_click=stepper.previous).props('flat') + ui.button('Next', on_click=stepper.next) + + +@ui.refreshable +def preview_config(): + """Preview the configuration file.""" + with ui.card().tight(): + preview = ui.code(yaml.dump(config), language='yaml') + + +preview_config() +# with ui.step('Ingredients'): +# ui.label('Mix the ingredients') +# with ui.stepper_navigation(): +# ui.button('Next', on_click=stepper.next) +# ui.button('Back', on_click=stepper.previous).props('flat') +# with ui.step('Bake'): +# ui.label('Bake for 20 minutes') +# with ui.stepper_navigation(): +# ui.button('Done', on_click=lambda: ui.notify('Yay!', type='positive')) +# ui.button('Back', on_click=stepper.previous).props('flat') + +ui.run() diff --git a/readme.md b/readme.md index 6f8a8dab..f95e24d4 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ ## Summary An open-source tool for measuring, monitoring and reporting on policy and spatial urban indicators for healthy, sustainable cities worldwide using open or custom data. Designed to support participation in the [Global Observatory of Healthy and Sustainable Cities](https://healthysustainablecities.org)' [1000 city challenge](https://www.healthysustainablecities.org/1000cities), it can be run as code or as an app in your web browser. View the full documentation of the Global Healthy and Sustainable City Indicators software at https://healthysustainablecities.github.io/software/. -This software can be configured to support comparisons within- and between-cities and across time, benchmarking, analysis and monitoring of local policies, tracking progress, and inform interventions towards achieving healthy, equitable and sustainable cities. It also support generating resources including maps, figures and reports in multiple languages, so these can be made accessible for use by local communities and stakeholders as a source of evidence to advocate for change. +This software can be configured to support comparisons within- and between-cities and across time, benchmarking, analysis and monitoring of local policies, tracking progress, and inform interventions towards achieving healthy, equitable and sustainable cities. It also support generating resources including maps, figures and reports in multiple languages, so these can be made accessible for use by local communities and stakeholders as a source of evidence to advocate for change. ![image](https://github.com/healthysustainablecities/global-indicators/assets/12984626/6f7d9c8a-47b2-496f-983b-820f5e86d0b5) @@ -146,6 +146,10 @@ To run the analysis for your study region visit our website for detailed instruc The software was developed by the [Global Healthy and Sustainable City Indicators Collaboration](https://www.healthysustainablecities.org/about#team) team, an international partnership of researchers and practitioners, extending methods developed by the [Healthy Liveable Cities Lab](https://cur.org.au/research-programs/healthy-liveable-cities-group/) at RMIT University and incorporating functionality from the [OSMnx](https://github.com/gboeing/osmnx) tool developed by Geoff Boeing. +The software may be cited as: + +Higgs C, Liu S, Boeing G, Arundel J, Lowe M, Adlakha D et al (2023) Global Healthy and Sustainable City Indicators software. https://doi.org/10.25439/rmt.24760260.v1 + The concept underlying the framework is described in: Liu S, Higgs C, Arundel J, Boeing G, Cerdera N, Moctezuma D, Cerin E, Adlakha D, Lowe M, Giles-Corti B (2022) A Generalized Framework for Measuring Pedestrian Accessibility around the World Using Open Data. Geographical Analysis. 54(3):559-582. https://doi.org/10.1111/gean.12290 @@ -162,7 +166,7 @@ Higgs, C. et al. (2022) ‘Policy-Relevant Spatial Indicators of Urban Liveabili This software is an officially sponsored Docker Open Source Software Project (https://hub.docker.com/u/globalhealthyliveablecities). The [broader programme of work this software supports](https://www.healthysustainablecities.org/about) received the Planning Institute of Australia's 2023 national award for Excellence in Planning Research. -Our approach, while supporting the optional use of custom data, was founded with an [open science ethos](https://www.unesco.org/en/open-science) and promotes the usage of global open data produced by individuals, organisations and governments including OpenStreetMap Contributors ([OpenStreetMap](https://wiki.openstreetmap.org/)), the European Commission Joint Research Centre ([Global Human Settlements Layer](https://ghsl.jrc.ec.europa.eu/)), and open data portals in general. We gratefully acknowledge the valuable contributions to transparency, equity and science open data initiatives such as these and the producers of open source software underlying our work bring to the world. +Our approach, while supporting the optional use of custom data, was founded with an [open science ethos](https://www.unesco.org/en/open-science) and promotes the usage of global open data produced by individuals, organisations and governments including OpenStreetMap Contributors ([OpenStreetMap](https://wiki.openstreetmap.org/)), the European Commission Joint Research Centre ([Global Human Settlements Layer](https://ghsl.jrc.ec.europa.eu/)), and open data portals in general. We gratefully acknowledge the valuable contributions to transparency, equity and science open data initiatives such as these and the producers of open source software underlying our work bring to the world. Open source software we have used and which is included in our software environment includes [Python](https://www.python.org/) (programming language), [Docker](https://www.docker.com/) (software containerisation), [Conda](https://anaconda.org/) (package management), [PostgreSQL](https://www.postgresql.org/) (database), [PostGIS](http://postgis.net/) (spatial database), [pgRouting](https://pgrouting.org/) (routing analysis), [GDAL/OGR](https://doi.org/10.5281/zenodo.5884351) (Geospatial Data Abstraction software Library), [OSMnx](https://doi.org/10.1016/j.compenvurbsys.2017.05.004) (OpenStreetMap retrieval and network analysis), [NetworkX](https://networkx.org/) (network analysis), [NiceGUI](https://doi.org/10.5281/zenodo.8083457) (graphical user interface), [Jupyter Lab](https://jupyter.org/) (scientific code notebooks), [Pandas](https://pandas.pydata.org/) (dataframes), [GeoPandas](https://geopandas.org/en/stable/) (spatial dataframes), [GeoAlchemy](https://github.com/geoalchemy/geoalchemy2) (spatial SQL management), [SQLAlchemy](https://www.sqlalchemy.org/) (SQL management), [Pandana](https://github.com/UDST/pandana) (network analysis using pandas), [Rasterio](https://rasterio.readthedocs.io/en/stable/) (raster analysis), [GTFS-Lite](https://github.com/wklumpen/gtfs-lite/tree/master) (GTFS parsing), [Git](https://git-scm.com/) (source code management), [GitHub](https://github.com/about) (development platform), [Leaflet](https://leafletjs.com/) and Fabio Crameri's [Scientific colour maps](https://doi.org/10.5281/zenodo.1243862).