openwalkscore 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openwalkscore-0.1.0/CHANGELOG.md +33 -0
- openwalkscore-0.1.0/CITATION.cff +20 -0
- openwalkscore-0.1.0/LICENSE +21 -0
- openwalkscore-0.1.0/MANIFEST.in +10 -0
- openwalkscore-0.1.0/PKG-INFO +401 -0
- openwalkscore-0.1.0/README.md +358 -0
- openwalkscore-0.1.0/examples/nightlife_profile.toml +19 -0
- openwalkscore-0.1.0/examples/quickstart.py +35 -0
- openwalkscore-0.1.0/pyproject.toml +133 -0
- openwalkscore-0.1.0/setup.cfg +4 -0
- openwalkscore-0.1.0/src/openwalkscore/__init__.py +82 -0
- openwalkscore-0.1.0/src/openwalkscore/_version.py +1 -0
- openwalkscore-0.1.0/src/openwalkscore/cli.py +258 -0
- openwalkscore-0.1.0/src/openwalkscore/dem.py +120 -0
- openwalkscore-0.1.0/src/openwalkscore/geo.py +106 -0
- openwalkscore-0.1.0/src/openwalkscore/htmlmap.py +196 -0
- openwalkscore-0.1.0/src/openwalkscore/network.py +405 -0
- openwalkscore-0.1.0/src/openwalkscore/osm.py +381 -0
- openwalkscore-0.1.0/src/openwalkscore/overpass.py +278 -0
- openwalkscore-0.1.0/src/openwalkscore/profiles.py +568 -0
- openwalkscore-0.1.0/src/openwalkscore/py.typed +0 -0
- openwalkscore-0.1.0/src/openwalkscore/scoring.py +708 -0
- openwalkscore-0.1.0/src/openwalkscore.egg-info/PKG-INFO +401 -0
- openwalkscore-0.1.0/src/openwalkscore.egg-info/SOURCES.txt +37 -0
- openwalkscore-0.1.0/src/openwalkscore.egg-info/dependency_links.txt +1 -0
- openwalkscore-0.1.0/src/openwalkscore.egg-info/entry_points.txt +2 -0
- openwalkscore-0.1.0/src/openwalkscore.egg-info/requires.txt +14 -0
- openwalkscore-0.1.0/src/openwalkscore.egg-info/top_level.txt +1 -0
- openwalkscore-0.1.0/tests/conftest.py +164 -0
- openwalkscore-0.1.0/tests/fixtures/custom_profile.toml +19 -0
- openwalkscore-0.1.0/tests/fixtures/slope.asc +9 -0
- openwalkscore-0.1.0/tests/fixtures/small.geojson +24 -0
- openwalkscore-0.1.0/tests/fixtures/small.osm +57 -0
- openwalkscore-0.1.0/tests/test_cli_html.py +233 -0
- openwalkscore-0.1.0/tests/test_geo_osm.py +200 -0
- openwalkscore-0.1.0/tests/test_network.py +187 -0
- openwalkscore-0.1.0/tests/test_overpass.py +191 -0
- openwalkscore-0.1.0/tests/test_profiles.py +168 -0
- openwalkscore-0.1.0/tests/test_scoring.py +325 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
|
|
5
|
+
uses [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-11-08
|
|
10
|
+
### Added
|
|
11
|
+
- First public release.
|
|
12
|
+
- **Scoring method**: nine weighted amenity categories (grocery, restaurants & cafes,
|
|
13
|
+
shopping, schools, parks & play, healthcare, banks & post, culture & community, transit
|
|
14
|
+
stops) with cosine distance decay (full credit up to 400 m, zero at 1.6 km) and
|
|
15
|
+
diminishing returns per category; penalties for low intersection density, long blocks
|
|
16
|
+
and, with a DEM, steep grades.
|
|
17
|
+
- **Network distances** on the OpenStreetMap pedestrian graph with a pure-Python
|
|
18
|
+
multi-source Dijkstra; points and amenities are snapped to the nearest street *edge*.
|
|
19
|
+
Straight-line distance x detour factor (1.3) is the fallback without a network.
|
|
20
|
+
Optional `networkx` engine (`pip install openwalkscore[networkx]`).
|
|
21
|
+
- **Data**: Overpass API client with timeouts, mirror fail-over, HTTP 429/5xx retry, on-disk
|
|
22
|
+
cache, process-wide rate limiting and the User-Agent `openwalkscore/<version>`. Offline
|
|
23
|
+
input from `.osm` XML, GeoJSON or saved Overpass JSON.
|
|
24
|
+
- **API**: `score_point`, `score_grid` (GeoJSON grid), `compare`, reusable `WalkScorer`,
|
|
25
|
+
explainable `WalkScoreResult` (subscores, counted amenities with distances, penalties,
|
|
26
|
+
network metrics, plain-text explanation).
|
|
27
|
+
- **Profiles**: built-in `default`, `family`, `senior`, `transit`; custom profiles from a
|
|
28
|
+
dict or TOML file, validated against typos.
|
|
29
|
+
- **CLI**: `openwalkscore score | grid | explain | compare | profiles`.
|
|
30
|
+
- **HTML maps**: self-contained Leaflet page (CDN with SRI hashes, OpenStreetMap tiles and
|
|
31
|
+
attribution), all text escaped.
|
|
32
|
+
- ESRI ASCII grid (`.asc`) reader for the slope penalty, or any `(lat, lon) -> metres`
|
|
33
|
+
callable.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
authors:
|
|
4
|
+
- family-names: "Eminoğlu"
|
|
5
|
+
given-names: "Yusuf"
|
|
6
|
+
email: "yusufeminoglu@gmail.com"
|
|
7
|
+
orcid: "https://orcid.org/0009-0005-6000-2934"
|
|
8
|
+
title: "openwalkscore"
|
|
9
|
+
version: "0.1.0"
|
|
10
|
+
date-released: "2026-11-08"
|
|
11
|
+
license: "MIT"
|
|
12
|
+
repository-code: "https://github.com/YusufEminoglu/openwalkscore_sdk"
|
|
13
|
+
url: "https://pypi.org/project/openwalkscore/"
|
|
14
|
+
abstract: "Open, transparent, API-key-free walkability scores (0-100) computed from OpenStreetMap: network-distance amenity access with distance decay and diminishing returns, plus street-connectivity and slope penalties."
|
|
15
|
+
keywords:
|
|
16
|
+
- walkability
|
|
17
|
+
- accessibility
|
|
18
|
+
- OpenStreetMap
|
|
19
|
+
- urban analytics
|
|
20
|
+
- 15-minute city
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yusuf Eminoğlu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
include LICENSE
|
|
2
|
+
include README.md
|
|
3
|
+
include CHANGELOG.md
|
|
4
|
+
include CITATION.cff
|
|
5
|
+
include pyproject.toml
|
|
6
|
+
include src/openwalkscore/py.typed
|
|
7
|
+
recursive-include src/openwalkscore *.py
|
|
8
|
+
recursive-include tests *.py
|
|
9
|
+
recursive-include tests/fixtures *.osm *.geojson *.asc *.toml
|
|
10
|
+
recursive-include examples *.py *.toml
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openwalkscore
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Open, transparent, API-key-free walkability scores (0-100) for any point or area, computed from OpenStreetMap.
|
|
5
|
+
Author-email: Yusuf Eminoğlu <yusufeminoglu@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/YusufEminoglu/openwalkscore_sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/YusufEminoglu/openwalkscore_sdk
|
|
9
|
+
Project-URL: Issues, https://github.com/YusufEminoglu/openwalkscore_sdk/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/YusufEminoglu/openwalkscore_sdk#readme
|
|
11
|
+
Project-URL: Changelog, https://github.com/YusufEminoglu/openwalkscore_sdk/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: walkability,walk-score,accessibility,15-minute-city,openstreetmap,overpass,urban-analytics,gis,proptech,real-estate,pedestrian,network-analysis,dijkstra,amenities
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
23
|
+
Classifier: Topic :: Sociology
|
|
24
|
+
Classifier: Intended Audience :: Science/Research
|
|
25
|
+
Classifier: Intended Audience :: Developers
|
|
26
|
+
Classifier: Intended Audience :: Education
|
|
27
|
+
Classifier: Environment :: Console
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: >=3.9
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Requires-Dist: certifi>=2023.7.22
|
|
33
|
+
Requires-Dist: tomli>=1.1.0; python_version < "3.11"
|
|
34
|
+
Provides-Extra: networkx
|
|
35
|
+
Requires-Dist: networkx>=2.8; extra == "networkx"
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
41
|
+
Requires-Dist: networkx>=2.8; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# openwalkscore
|
|
45
|
+
|
|
46
|
+
**Open, transparent, API-key-free walkability scores (0-100) for any point or area on Earth,
|
|
47
|
+
computed from OpenStreetMap.**
|
|
48
|
+
|
|
49
|
+
[](https://github.com/YusufEminoglu/openwalkscore_sdk/actions/workflows/ci.yml)
|
|
50
|
+
[](https://pypi.org/project/openwalkscore/)
|
|
51
|
+
[](https://pypi.org/project/openwalkscore/)
|
|
52
|
+
[](LICENSE)
|
|
53
|
+
|
|
54
|
+
`openwalkscore` measures how many everyday destinations (groceries, cafes, schools, parks,
|
|
55
|
+
clinics, transit stops and more) you can reach on foot, and how walkable the street network
|
|
56
|
+
around you is. Every number in the method is published below and lives in an editable
|
|
57
|
+
profile, so a score can always be reproduced, audited and adapted.
|
|
58
|
+
|
|
59
|
+
It is written for urban data scientists, real-estate and proptech developers, planners and
|
|
60
|
+
students who need a walkability indicator they can explain, and that works for any city.
|
|
61
|
+
|
|
62
|
+
> **Disclaimer.** openwalkscore is an independent open-source project. It is **not
|
|
63
|
+
> affiliated with, endorsed by or connected to Walk Score® or Redfin**. Walk Score® is a
|
|
64
|
+
> registered trademark of its owner. openwalkscore uses its own method and its own data
|
|
65
|
+
> (OpenStreetMap); **its scores are not comparable to Walk Score® values** and must not be
|
|
66
|
+
> presented as such.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Contents
|
|
71
|
+
|
|
72
|
+
- [Install](#install)
|
|
73
|
+
- [Quick start](#quick-start)
|
|
74
|
+
- [Methodology](#methodology)
|
|
75
|
+
- [Profiles](#profiles)
|
|
76
|
+
- [Python API](#python-api)
|
|
77
|
+
- [Command line](#command-line)
|
|
78
|
+
- [Data sources and offline use](#data-sources-and-offline-use)
|
|
79
|
+
- [HTML maps](#html-maps)
|
|
80
|
+
- [Limitations](#limitations)
|
|
81
|
+
- [Citing](#citing)
|
|
82
|
+
- [License and attribution](#license-and-attribution)
|
|
83
|
+
|
|
84
|
+
## Install
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install openwalkscore
|
|
88
|
+
# optional: run shortest paths with networkx instead of the built-in Dijkstra
|
|
89
|
+
pip install "openwalkscore[networkx]"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Python 3.9 or newer. The only runtime dependency is `certifi` (plus `tomli` on Python < 3.11).
|
|
93
|
+
No API key, no account, no `osmnx`/`geopandas` stack required.
|
|
94
|
+
|
|
95
|
+
## Quick start
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from openwalkscore import score_point
|
|
99
|
+
|
|
100
|
+
r = score_point(48.8530, 2.3499) # Ile de la Cite, Paris
|
|
101
|
+
print(r.score, r.rating) # e.g. 100 Excellent
|
|
102
|
+
print(r.subscores) # {'grocery': 100.0, 'restaurants_cafes': 100.0, ...}
|
|
103
|
+
print(r.explain()) # plain-text, line-by-line justification
|
|
104
|
+
|
|
105
|
+
for hit in r.nearest_amenities(limit=5):
|
|
106
|
+
print(f"{hit.distance_m:5.0f} m {hit.category:<18} {hit.name}")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The first call downloads amenities and the walking network around the point from the
|
|
110
|
+
public Overpass API (this can take up to a minute in a dense city centre) and caches the
|
|
111
|
+
response on disk; repeated calls are instant.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
openwalkscore score --lat 48.8530 --lon 2.3499
|
|
115
|
+
openwalkscore explain --lat 48.8530 --lon 2.3499
|
|
116
|
+
openwalkscore grid --bbox 2.340,48.850,2.360,48.860 --cell 200 -o paris.geojson --html paris.html
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Methodology
|
|
120
|
+
|
|
121
|
+
For a point *p* and a profile with categories *c* (weight *W_c*):
|
|
122
|
+
|
|
123
|
+
### 1. Amenity categories
|
|
124
|
+
|
|
125
|
+
Each OpenStreetMap feature with a location (node, or way/relation centroid) is assigned to the
|
|
126
|
+
**first** category whose tag rules match (`shop=*` comes after the grocery and healthcare
|
|
127
|
+
rules, so a supermarket counts as grocery, not shopping). Default profile:
|
|
128
|
+
|
|
129
|
+
| Category | Weight | Counted (n) | Ratio (r) | Tags (abridged) |
|
|
130
|
+
|---|---:|---:|---:|---|
|
|
131
|
+
| Grocery | 3 | 2 | 0.4 | `shop=supermarket/convenience/greengrocer/bakery/butcher/deli`, `amenity=marketplace` |
|
|
132
|
+
| Restaurants & cafes | 3 | 10 | 0.8 | `amenity=restaurant/cafe/fast_food/pub/bar/food_court/ice_cream` |
|
|
133
|
+
| Healthcare | 1 | 2 | 0.5 | `amenity=pharmacy/clinic/doctors/hospital/dentist`, `shop=chemist`, `healthcare=*` |
|
|
134
|
+
| Shopping | 2 | 5 | 0.7 | `shop=*` (except vacant, car, car_parts, fuel) |
|
|
135
|
+
| Schools | 1 | 1 | - | `amenity=school/kindergarten/childcare/college/university` |
|
|
136
|
+
| Parks & play | 1 | 2 | 0.5 | `leisure=park/playground/garden/recreation_ground/nature_reserve` (not `access=private`) |
|
|
137
|
+
| Banks & post | 1 | 1 | - | `amenity=bank/atm/bureau_de_change/post_office` |
|
|
138
|
+
| Culture & community | 1 | 2 | 0.5 | `amenity=library/theatre/cinema/arts_centre/community_centre`, `tourism=museum/gallery` |
|
|
139
|
+
| Transit stops | 2 | 3 | 0.5 | `highway=bus_stop`, `public_transport=platform`, `railway=station/halt/tram_stop/subway_entrance`, `amenity=ferry_terminal` |
|
|
140
|
+
|
|
141
|
+
Run `openwalkscore explain` (no coordinates) to print the complete profile as JSON.
|
|
142
|
+
|
|
143
|
+
### 2. Walking distance
|
|
144
|
+
|
|
145
|
+
* The walking network is built from OSM ways with a walkable `highway=*` value (footway,
|
|
146
|
+
path, pedestrian, steps, living_street, residential, service, tertiary ... primary, track,
|
|
147
|
+
cycleway, ...). Motorways and trunk roads are excluded unless `foot=yes`; `foot=no`,
|
|
148
|
+
`access=private` and `foot=use_sidepath` are respected.
|
|
149
|
+
* Every pair of consecutive way nodes is an undirected edge weighted by its great-circle
|
|
150
|
+
length.
|
|
151
|
+
* The scored point and every amenity are **snapped to the nearest edge** (not node), and the
|
|
152
|
+
perpendicular access legs are added to the route.
|
|
153
|
+
* Shortest paths come from a pure-Python multi-source **Dijkstra** that starts part-way along
|
|
154
|
+
the origin edge and stops at the decay cut-off (1.6 km). `engine="networkx"` gives
|
|
155
|
+
identical results with networkx.
|
|
156
|
+
* Fallback: amenities more than `max_snap_m` (250 m) from the network, or every amenity when
|
|
157
|
+
the point itself has no network nearby, use **straight-line distance x detour factor 1.3**.
|
|
158
|
+
The result reports `distance_mode` (`network`, `straight-line` or `mixed`) per amenity
|
|
159
|
+
and overall.
|
|
160
|
+
* An amenity that is close but not connected to the point's network (for example across a
|
|
161
|
+
river with no bridge in the data) gets no credit.
|
|
162
|
+
|
|
163
|
+
### 3. Distance decay
|
|
164
|
+
|
|
165
|
+
Credit for an amenity at walking distance *d*:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
f(d) = 1 if d <= 400 m
|
|
169
|
+
f(d) = 0.5 * (1 + cos(pi * t)) if 400 m < d < 1600 m, t = (d - 400) / 1200
|
|
170
|
+
f(d) = 0 if d >= 1600 m
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The cosine taper is smooth at both ends: an amenity 700 m away earns 0.85, 1 km away 0.50,
|
|
174
|
+
1.3 km away 0.15. A linear ramp is available (`decay.shape = "linear"`).
|
|
175
|
+
|
|
176
|
+
### 4. Diminishing returns
|
|
177
|
+
|
|
178
|
+
Within a category the amenities are ranked by credit and the best *n* are kept. The *k*-th
|
|
179
|
+
one is weighted by
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
s_k = r^(k-1) / (1 + r + ... + r^(n-1))
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
so the shares sum to 1 and each extra cafe adds less than the previous one. The category
|
|
186
|
+
subscore is `S_c = sum_k s_k * f(d_k)`, between 0 and 1. Example (restaurants & cafes,
|
|
187
|
+
n = 10, r = 0.8): one cafe next door is worth 22 % of the category, five cafes 75 %, ten
|
|
188
|
+
cafes 100 %.
|
|
189
|
+
|
|
190
|
+
### 5. Access score
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
A = 100 * sum_c W_c * S_c / sum_c W_c
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 6. Network penalties
|
|
197
|
+
|
|
198
|
+
Measured within `analysis_radius_m` (800 m) of the point:
|
|
199
|
+
|
|
200
|
+
| Penalty | Metric | No penalty at | Full penalty at | Max |
|
|
201
|
+
|---|---|---:|---:|---:|
|
|
202
|
+
| Intersection density | nodes where >= 3 street edges meet, per km² | >= 90 /km² | <= 30 /km² | 10 % |
|
|
203
|
+
| Block length | mean length of street segments between intersections / dead ends | <= 120 m | >= 240 m | 10 % |
|
|
204
|
+
| Slope (only with a DEM) | length-weighted mean absolute grade of those segments | <= 3 % | >= 15 % | 10 % |
|
|
205
|
+
|
|
206
|
+
Each penalty is linear between its two thresholds. They combine multiplicatively,
|
|
207
|
+
`P = 1 - prod_i (1 - P_i)`, capped at `max_total_penalty` (30 %). Penalties are skipped
|
|
208
|
+
(and the explanation says so) when the point has no network nearby.
|
|
209
|
+
|
|
210
|
+
### 7. Final score
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
score = round(A * (1 - P)) # integer 0..100
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
| Score | Rating |
|
|
217
|
+
|---:|---|
|
|
218
|
+
| 90-100 | Excellent |
|
|
219
|
+
| 70-89 | High |
|
|
220
|
+
| 50-69 | Moderate |
|
|
221
|
+
| 25-49 | Low |
|
|
222
|
+
| 0-24 | Very low |
|
|
223
|
+
|
|
224
|
+
## Profiles
|
|
225
|
+
|
|
226
|
+
Built-in profiles: `default`, `family` (schools, parks and healthcare weighted up),
|
|
227
|
+
`senior` (300 m full credit, 1 km cut-off, healthcare and transit weighted up, stronger slope
|
|
228
|
+
penalty) and `transit` (transit stops dominate).
|
|
229
|
+
|
|
230
|
+
A custom profile overrides a base profile key by key, from a dict or a TOML file:
|
|
231
|
+
|
|
232
|
+
```toml
|
|
233
|
+
# nightlife.toml
|
|
234
|
+
[profile]
|
|
235
|
+
name = "nightlife"
|
|
236
|
+
base = "default"
|
|
237
|
+
|
|
238
|
+
[profile.decay]
|
|
239
|
+
full_m = 300
|
|
240
|
+
zero_m = 1200
|
|
241
|
+
shape = "linear"
|
|
242
|
+
|
|
243
|
+
[profile.categories.restaurants_cafes]
|
|
244
|
+
weight = 6
|
|
245
|
+
max_count = 12
|
|
246
|
+
|
|
247
|
+
[profile.categories.nightclubs] # a new category
|
|
248
|
+
weight = 2
|
|
249
|
+
tags = ["amenity=nightclub", "amenity=music_venue"]
|
|
250
|
+
max_count = 3
|
|
251
|
+
ratio = 0.6
|
|
252
|
+
|
|
253
|
+
[profile.penalties.slope]
|
|
254
|
+
max_penalty = 0.15
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from openwalkscore import Profile, score_point
|
|
259
|
+
|
|
260
|
+
p = Profile.from_toml("nightlife.toml")
|
|
261
|
+
p = Profile.from_dict({"categories": {"shopping": {"weight": 0}}}) # switch a category off
|
|
262
|
+
score_point(52.52, 13.405, profile=p)
|
|
263
|
+
score_point(52.52, 13.405, profile="nightlife.toml") # a path works too
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Unknown keys raise `ValueError`, so a typo never silently changes a score. Set
|
|
267
|
+
`replace_categories = true` to start from an empty category list.
|
|
268
|
+
|
|
269
|
+
## Python API
|
|
270
|
+
|
|
271
|
+
| Function / class | Purpose |
|
|
272
|
+
|---|---|
|
|
273
|
+
| `score_point(lat, lon, profile="default", *, data=None, dem=None, engine="python", client=None, use_cache=True, name="")` | Score one point; returns `WalkScoreResult`. |
|
|
274
|
+
| `score_grid(bbox, cell_m=250, profile="default", *, data=None, ...)` | Score square cells over `bbox = (west, south, east, north)`; returns a GeoJSON `FeatureCollection` with `score`, `rating`, `sub_<category>` per cell. |
|
|
275
|
+
| `compare(points, profile="default", *, data=None, ...)` | Score `(lat, lon)`, `(name, lat, lon)` or `{"name", "lat", "lon"}` items; close points share one download. |
|
|
276
|
+
| `WalkScorer(data, profile, dem=None, engine="python")` | Build the network once, call `.score(lat, lon)` / `.grid(bbox, cell_m)` many times. |
|
|
277
|
+
| `WalkScoreResult` | `score`, `rating`, `access_score`, `total_penalty`, `subscores`, `categories` (with counted `AmenityHit`s), `penalties`, `metrics`, `distance_mode`, `explain()`, `nearest_amenities()`, `to_dict()`, `to_geojson_feature()`. |
|
|
278
|
+
| `Profile`, `get_profile`, `list_profiles` | Profiles and their configuration. |
|
|
279
|
+
| `OverpassClient(endpoints, timeout_s, cache_dir, cache_ttl_s, min_interval_s, retries)` | Download control. |
|
|
280
|
+
| `load_osm(path_or_obj)` | Read `.osm`, `.geojson`, Overpass `.json`, or a dict. |
|
|
281
|
+
| `WalkNetwork`, `dijkstra` | The graph and the shortest-path routine, usable on their own. |
|
|
282
|
+
| `DEM.from_ascii_grid(path)`, `DEM.from_function(fn)` | Elevation for the slope penalty. |
|
|
283
|
+
| `render_html(obj)`, `write_html(obj, path)` | Leaflet map of a result, a list of results or a grid. |
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
from openwalkscore import WalkScorer, load_osm
|
|
287
|
+
|
|
288
|
+
scorer = WalkScorer(load_osm("district.osm"), profile="family")
|
|
289
|
+
results = [scorer.score(lat, lon) for lat, lon in listings] # fast: graph built once
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Using a DEM:
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
score_point(41.03, 28.98, dem="istanbul_wgs84.asc") # ESRI ASCII grid in degrees
|
|
296
|
+
|
|
297
|
+
import rasterio # or any sampler you like
|
|
298
|
+
src = rasterio.open("dem_wgs84.tif"); band = src.read(1)
|
|
299
|
+
def elevation(lat, lon):
|
|
300
|
+
row, col = src.index(lon, lat)
|
|
301
|
+
return float(band[row, col])
|
|
302
|
+
score_point(41.03, 28.98, dem=elevation)
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Command line
|
|
306
|
+
|
|
307
|
+
```text
|
|
308
|
+
openwalkscore score --lat LAT --lon LON [--profile P] [--json] [--html map.html]
|
|
309
|
+
openwalkscore explain [--lat LAT --lon LON] [--profile P]
|
|
310
|
+
openwalkscore grid --bbox WEST,SOUTH,EAST,NORTH --cell 250 -o grid.geojson [--html map.html]
|
|
311
|
+
openwalkscore compare --point "Home,41.03,28.98" --point "Office,41.04,29.00" [--json]
|
|
312
|
+
openwalkscore profiles
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Common options: `--input FILE` (offline `.osm`/`.geojson`/`.json`), `--dem FILE.asc`,
|
|
316
|
+
`--engine python|networkx`, `--timeout SECONDS`, `--endpoint URL` (repeatable),
|
|
317
|
+
`--no-cache`, `--cache-dir DIR`. Exit codes: 0 ok, 2 invalid input, 3 download failed.
|
|
318
|
+
|
|
319
|
+
`explain` without coordinates prints the method summary and the full profile.
|
|
320
|
+
|
|
321
|
+
## Data sources and offline use
|
|
322
|
+
|
|
323
|
+
**Overpass API (default).** `OverpassClient` sends one query per area (amenities with
|
|
324
|
+
`out center`, plus the walkable ways and their nodes) to public Overpass instances, trying
|
|
325
|
+
`overpass-api.de`, `overpass.kumi.systems` and `overpass.private.coffee` in turn. It:
|
|
326
|
+
|
|
327
|
+
* sets the generic User-Agent `openwalkscore/<version>`;
|
|
328
|
+
* applies a network timeout (default 60 s) and retries HTTP 429/502/503/504 with back-off
|
|
329
|
+
before failing over to the next mirror;
|
|
330
|
+
* waits at least 1 s between requests from the same process;
|
|
331
|
+
* caches responses on disk for 7 days (`%LOCALAPPDATA%\openwalkscore\cache`,
|
|
332
|
+
`~/.cache/openwalkscore`, or `OPENWALKSCORE_CACHE_DIR`);
|
|
333
|
+
* refuses areas with a diagonal above 25 km. For a whole city, download an extract once
|
|
334
|
+
(e.g. from Geofabrik, then clip with `osmium`) and pass it with `data=` / `--input`.
|
|
335
|
+
|
|
336
|
+
Please follow the [Overpass usage policy](https://dev.overpass-api.de/overpass-doc/en/preface/commons.html):
|
|
337
|
+
keep areas small and avoid parallel bulk jobs.
|
|
338
|
+
|
|
339
|
+
**Offline input.** `data=` (or `--input`) accepts:
|
|
340
|
+
|
|
341
|
+
* `.osm` / `.xml` - OSM XML (JOSM, OSM API, `osmium cat -f osm`);
|
|
342
|
+
* Overpass JSON (`{"elements": [...]}`) saved from overpass-turbo or the API;
|
|
343
|
+
* GeoJSON - points become amenities, `LineString`s with a `highway` property become the
|
|
344
|
+
network (touching vertices are joined), polygons become amenities at their centroid.
|
|
345
|
+
Tags are read from `properties` or `properties.tags`.
|
|
346
|
+
|
|
347
|
+
## HTML maps
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
from openwalkscore import score_point, score_grid, write_html
|
|
351
|
+
write_html(score_point(40.4168, -3.7038), "madrid.html")
|
|
352
|
+
write_html(score_grid((-3.71, 40.41, -3.69, 40.425), 200), "madrid_grid.html")
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
The page loads Leaflet 1.9.4 from unpkg with Subresource Integrity hashes and shows
|
|
356
|
+
OpenStreetMap tiles with the required attribution. Data is embedded as JSON with `<`, `>`
|
|
357
|
+
and `&` escaped, and all OSM-supplied text (names, tags) is inserted with `textContent`, so
|
|
358
|
+
feature names cannot inject markup or script.
|
|
359
|
+
|
|
360
|
+
## Limitations
|
|
361
|
+
|
|
362
|
+
* **OSM completeness varies.** Missing shops or footpaths lower the score; a score is a
|
|
363
|
+
statement about the map as much as about the place. Compare places within regions of
|
|
364
|
+
similar mapping quality.
|
|
365
|
+
* **Sidewalks mapped as separate footways** add intersections and short segments; the
|
|
366
|
+
density and block-length penalties are therefore lenient in such areas by design (they
|
|
367
|
+
rarely trigger where sidewalks are mapped). Penalties near the edge of a local extract
|
|
368
|
+
can be overstated if the extract does not cover `analysis_radius_m`.
|
|
369
|
+
* No crossing safety, traffic speed, sidewalk width, lighting, opening hours, shade,
|
|
370
|
+
crime or amenity quality is modelled.
|
|
371
|
+
* The network is treated as undirected and of uniform walking speed; steps and steep grades
|
|
372
|
+
only enter through the optional slope penalty.
|
|
373
|
+
* Amenity counts are capped per category; a mall with 200 shops counts as at most 5 shops
|
|
374
|
+
in the default profile.
|
|
375
|
+
|
|
376
|
+
## Development
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
git clone https://github.com/YusufEminoglu/openwalkscore_sdk
|
|
380
|
+
cd openwalkscore_sdk
|
|
381
|
+
pip install -e ".[dev]"
|
|
382
|
+
python -m pytest # no network needed: tests use hand-made OSM fixtures
|
|
383
|
+
python -m ruff check . && python -m ruff format --check src tests && python -m mypy src
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Citing
|
|
387
|
+
|
|
388
|
+
If you use openwalkscore in research, please cite it via [CITATION.cff](CITATION.cff):
|
|
389
|
+
|
|
390
|
+
> Eminoğlu, Y. (2026). *openwalkscore: open, transparent walkability scores from
|
|
391
|
+
> OpenStreetMap* (version 0.1.0) [Software]. https://github.com/YusufEminoglu/openwalkscore_sdk
|
|
392
|
+
|
|
393
|
+
## License and attribution
|
|
394
|
+
|
|
395
|
+
Code: MIT, © 2026 Yusuf Eminoğlu. See [LICENSE](LICENSE).
|
|
396
|
+
|
|
397
|
+
Map data: © [OpenStreetMap contributors](https://www.openstreetmap.org/copyright), available
|
|
398
|
+
under the Open Database License (ODbL). When you publish scores or maps derived from OSM
|
|
399
|
+
data, credit OpenStreetMap accordingly.
|
|
400
|
+
|
|
401
|
+
Walk Score® is a registered trademark of its owner; this project is not affiliated with it.
|