isovist 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.
- isovist-0.1.0/CHANGELOG.md +22 -0
- isovist-0.1.0/CITATION.cff +20 -0
- isovist-0.1.0/LICENSE +21 -0
- isovist-0.1.0/MANIFEST.in +9 -0
- isovist-0.1.0/PKG-INFO +259 -0
- isovist-0.1.0/README.md +223 -0
- isovist-0.1.0/examples/plaza.geojson +104 -0
- isovist-0.1.0/examples/plaza_boundary.geojson +38 -0
- isovist-0.1.0/examples/quickstart.py +72 -0
- isovist-0.1.0/pyproject.toml +109 -0
- isovist-0.1.0/setup.cfg +4 -0
- isovist-0.1.0/src/isovist/__init__.py +60 -0
- isovist-0.1.0/src/isovist/__main__.py +7 -0
- isovist-0.1.0/src/isovist/_version.py +1 -0
- isovist-0.1.0/src/isovist/cli.py +145 -0
- isovist-0.1.0/src/isovist/core.py +507 -0
- isovist-0.1.0/src/isovist/environment.py +304 -0
- isovist-0.1.0/src/isovist/geometry.py +171 -0
- isovist-0.1.0/src/isovist/io.py +182 -0
- isovist-0.1.0/src/isovist/measures.py +133 -0
- isovist-0.1.0/src/isovist/py.typed +0 -0
- isovist-0.1.0/src/isovist/vga.py +200 -0
- isovist-0.1.0/src/isovist.egg-info/PKG-INFO +259 -0
- isovist-0.1.0/src/isovist.egg-info/SOURCES.txt +33 -0
- isovist-0.1.0/src/isovist.egg-info/dependency_links.txt +1 -0
- isovist-0.1.0/src/isovist.egg-info/entry_points.txt +2 -0
- isovist-0.1.0/src/isovist.egg-info/requires.txt +7 -0
- isovist-0.1.0/src/isovist.egg-info/top_level.txt +1 -0
- isovist-0.1.0/tests/conftest.py +24 -0
- isovist-0.1.0/tests/test_geometry.py +35 -0
- isovist-0.1.0/tests/test_io_cli.py +225 -0
- isovist-0.1.0/tests/test_isovist.py +295 -0
- isovist-0.1.0/tests/test_measures.py +102 -0
- isovist-0.1.0/tests/test_vga.py +128 -0
- isovist-0.1.0/tests/test_visibility.py +103 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-11-09
|
|
8
|
+
### Added
|
|
9
|
+
- `Environment` of obstacle polygons (with holes, overlaps allowed) and an optional
|
|
10
|
+
enclosing boundary; exact point location with a data-scaled tolerance.
|
|
11
|
+
- Exact isovist polygon by an angular sweep over critical directions (edge endpoints,
|
|
12
|
+
polygon crossings, view-distance circle crossings, field-of-view limits) with exact
|
|
13
|
+
ray/edge-line intersections; optional `max_radius`, `fov` and `direction`.
|
|
14
|
+
- Isovist measures with closed-form integrals: area, perimeter, compactness,
|
|
15
|
+
jaggedness, occlusivity and number of occluding edges, drift (distance and angle),
|
|
16
|
+
min/max/mean/std radial length.
|
|
17
|
+
- Exact point-to-point intervisibility (corner grazing allowed, interior crossings
|
|
18
|
+
blocked) and a vectorised intervisibility matrix.
|
|
19
|
+
- Visibility graph analysis on grids or explicit points: connectivity, clustering
|
|
20
|
+
coefficient, mean depth, relative asymmetry and Hillier-Hanson integration.
|
|
21
|
+
- GeoJSON input/output for projected coordinates; `isovist` command line tool with
|
|
22
|
+
`compute`, `vga` and `visible` sub-commands.
|
|
@@ -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: "geospacephilo@gmail.com"
|
|
7
|
+
orcid: "https://orcid.org/0009-0005-6000-2934"
|
|
8
|
+
title: "isovist"
|
|
9
|
+
version: "0.1.0"
|
|
10
|
+
date-released: "2026-11-09"
|
|
11
|
+
license: "MIT"
|
|
12
|
+
repository-code: "https://github.com/YusufEminoglu/isovist_sdk"
|
|
13
|
+
url: "https://pypi.org/project/isovist/"
|
|
14
|
+
abstract: "Exact 2D isovists from a viewpoint among obstacle polygons (angular sweep with exact edge intersections, optional view distance and field of view), isovist shape measures (area, perimeter, compactness, occlusivity, drift, radial statistics, jaggedness), exact point-to-point intervisibility and visibility graph analysis (connectivity, clustering coefficient, mean depth, integration), with GeoJSON input/output and a command-line tool."
|
|
15
|
+
keywords:
|
|
16
|
+
- isovist
|
|
17
|
+
- visibility graph analysis
|
|
18
|
+
- space syntax
|
|
19
|
+
- urban design
|
|
20
|
+
- gis
|
isovist-0.1.0/LICENSE
ADDED
|
@@ -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.
|
isovist-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: isovist
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Exact 2D isovists, isovist shape measures, point-to-point intervisibility and visibility graph analysis (VGA) for urban design, with GeoJSON in/out and a CLI.
|
|
5
|
+
Author-email: Yusuf Eminoğlu <geospacephilo@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/YusufEminoglu/isovist_sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/YusufEminoglu/isovist_sdk
|
|
9
|
+
Project-URL: Issues, https://github.com/YusufEminoglu/isovist_sdk/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/YusufEminoglu/isovist_sdk/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: isovist,visibility,visibility-graph-analysis,vga,space-syntax,urban-design,architecture,intervisibility,viewshed,geojson,gis,computational-geometry
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Environment :: Console
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
23
|
+
Classifier: Intended Audience :: Science/Research
|
|
24
|
+
Classifier: Intended Audience :: Developers
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: numpy>=1.20
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# isovist
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/isovist/)
|
|
40
|
+
[](https://pypi.org/project/isovist/)
|
|
41
|
+
[](https://github.com/YusufEminoglu/isovist_sdk/actions/workflows/ci.yml)
|
|
42
|
+
[](https://github.com/YusufEminoglu/isovist_sdk/blob/main/LICENSE)
|
|
43
|
+
|
|
44
|
+
**Exact 2D isovists and visibility graph analysis for urban design.**
|
|
45
|
+
|
|
46
|
+
`isovist` computes what can be seen from a point in a plan of buildings, walls
|
|
47
|
+
and columns, and describes the shape of that visible space with the classic
|
|
48
|
+
isovist measures. It also runs visibility graph analysis (VGA) on a grid of
|
|
49
|
+
points and answers point-to-point "can A see B?" questions. The only dependency
|
|
50
|
+
is NumPy.
|
|
51
|
+
|
|
52
|
+
- **Exact isovist polygon** from an angular sweep with exact ray/edge
|
|
53
|
+
intersections, not a fan of sampled rays. Optional maximum view distance and
|
|
54
|
+
field of view (angle and direction).
|
|
55
|
+
- **Isovist measures**: area, perimeter, compactness, jaggedness, occlusivity,
|
|
56
|
+
number of occluding edges, drift (distance and direction) and min/max/mean/std
|
|
57
|
+
radial length. All of them come from closed-form integrals.
|
|
58
|
+
- **Intervisibility**: exact; a sight line may graze a corner or run along a
|
|
59
|
+
wall, but it may not cross an obstacle.
|
|
60
|
+
- **Visibility graph analysis**: connectivity, clustering coefficient, mean
|
|
61
|
+
depth, relative asymmetry and integration on grids or on your own points.
|
|
62
|
+
An optional visual radius limits the links.
|
|
63
|
+
- **GeoJSON in/out** in projected coordinates, plus an `isovist` command-line tool.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install isovist
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Requires Python 3.9 or newer.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Quick start (Python)
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from isovist import Environment
|
|
77
|
+
|
|
78
|
+
room = [(0, 0), (10, 0), (10, 10), (0, 10)]
|
|
79
|
+
pillar = [(4, 4), (6, 4), (6, 6), (4, 6)]
|
|
80
|
+
env = Environment(obstacles=[pillar], boundary=room)
|
|
81
|
+
|
|
82
|
+
iso = env.isovist((2, 5))
|
|
83
|
+
iso.area # 70.0 (100 - pillar 4 - shadow 26)
|
|
84
|
+
iso.occlusivity # 13.416 (two occluding edges of length sqrt(45))
|
|
85
|
+
iso.polygon # [(x, y), ...] counter-clockwise ring
|
|
86
|
+
|
|
87
|
+
m = iso.measures()
|
|
88
|
+
m.compactness, m.drift, m.mean_radial, m.max_radial
|
|
89
|
+
|
|
90
|
+
env.isovist((2, 5), fov=90, direction=0, max_radius=6) # 90 deg view cone, 6 m reach
|
|
91
|
+
env.is_visible((3, 5), (7, 5)) # False: the pillar is in the way
|
|
92
|
+
|
|
93
|
+
from isovist import visibility_graph_analysis
|
|
94
|
+
vga = visibility_graph_analysis(env, spacing=1.0)
|
|
95
|
+
vga.connectivity, vga.clustering, vga.mean_depth, vga.integration
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Obstacles and the boundary are `isovist.Polygon` objects (holes allowed) or plain
|
|
99
|
+
vertex lists. Obstacles may overlap each other and may stick out of the boundary.
|
|
100
|
+
Holes in the boundary act as obstacles. The viewpoint must lie strictly in free
|
|
101
|
+
space.
|
|
102
|
+
|
|
103
|
+
## Quick start (command line)
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# isovists with all measures for two viewpoints and for every Point in a file
|
|
107
|
+
isovist compute --obstacles buildings.geojson --boundary site.geojson \
|
|
108
|
+
--at 512340.5,4512200 --points viewpoints.geojson -o isovists.geojson
|
|
109
|
+
|
|
110
|
+
# 120 degree view cone towards north-east, 80 m reach
|
|
111
|
+
isovist compute --obstacles buildings.geojson --at 512340.5,4512200 \
|
|
112
|
+
--fov 120 --direction 45 --max-radius 80
|
|
113
|
+
|
|
114
|
+
# VGA on a 2 m grid (writes grid points with the measures)
|
|
115
|
+
isovist vga --obstacles buildings.geojson --boundary site.geojson --spacing 2 -o vga.geojson
|
|
116
|
+
|
|
117
|
+
# intervisibility; exit code 0 = visible, 1 = not visible
|
|
118
|
+
isovist visible --obstacles buildings.geojson --from 10,20 --to 80,45
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Write negative coordinates as `--at=-12.5,40` so they are not read as options.
|
|
122
|
+
Without `--boundary`, open-space isovists need `--max-radius`.
|
|
123
|
+
|
|
124
|
+
See `examples/quickstart.py` for a small plaza with two pavilions and a column.
|
|
125
|
+
|
|
126
|
+
## Coordinates and GeoJSON
|
|
127
|
+
|
|
128
|
+
All geometry is planar, so coordinates must be **projected** (metres or feet),
|
|
129
|
+
for example UTM. RFC 7946 GeoJSON is nominally longitude/latitude. `isovist`
|
|
130
|
+
reads and writes the same structure with projected coordinates, as produced by
|
|
131
|
+
`ogr2ogr -t_srs EPSG:32635 -lco RFC7946=NO out.geojson in.gpkg`. A legacy
|
|
132
|
+
`crs` member in the input is copied to the output. `load_environment` warns when
|
|
133
|
+
the coordinates look like degrees.
|
|
134
|
+
|
|
135
|
+
Outputs: `isovist compute` writes Polygon features with the viewpoint
|
|
136
|
+
(`vp_x`, `vp_y`), the input point properties and every measure. `isovist vga`
|
|
137
|
+
writes Point features with `connectivity`, `clustering`, `mean_depth`,
|
|
138
|
+
`relative_asymmetry`, `integration` and `node_count`. Undefined values
|
|
139
|
+
(`nan`, `inf`) become `null`.
|
|
140
|
+
|
|
141
|
+
## API overview
|
|
142
|
+
|
|
143
|
+
| Object / function | Purpose |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `Environment(obstacles, boundary=None, tol=None)` | the plan; `.isovist()`, `.is_visible()`, `.visibility_matrix()`, `.locate()` |
|
|
146
|
+
| `Environment.isovist(vp, max_radius=None, fov=None, direction=0, arc_step=1)` | exact `Isovist` |
|
|
147
|
+
| `Isovist` | `.polygon`, `.area`, `.perimeter`, `.centroid`, `.occlusivity`, `.occluding_edges`, `.radial(theta)`, `.contains(pt)`, `.measures()`, `.to_geojson()` |
|
|
148
|
+
| `IsovistMeasures` | area, perimeter, compactness, jaggedness, occlusivity, occluding_edges, drift, drift_angle, min/max/mean/std_radial |
|
|
149
|
+
| `visibility_graph_analysis(env, points=None, spacing=None, max_radius=None)` | `VGAResult` with per-node arrays and `.records()` |
|
|
150
|
+
| `grid_points(env, spacing, bounds=None, origin=None)` | cell-centre grid in free space |
|
|
151
|
+
| `graph_measures(adjacency)` | VGA measures for any undirected graph |
|
|
152
|
+
| `load_environment`, `polygons_from_geojson`, `points_from_geojson`, `feature_collection`, `write_geojson` | GeoJSON I/O |
|
|
153
|
+
|
|
154
|
+
Angles are in degrees, counted counter-clockwise from east (+x), in both the
|
|
155
|
+
API and the CLI. `Isovist.radial` takes radians.
|
|
156
|
+
|
|
157
|
+
## Method and sources
|
|
158
|
+
|
|
159
|
+
**Isovist.** An isovist is the set of points visible from a vantage point
|
|
160
|
+
(Benedikt 1979). Every edge end point, every crossing between overlapping
|
|
161
|
+
polygons, every point where an edge meets the view-distance circle and each
|
|
162
|
+
field-of-view limit gives a *critical direction*. Between two consecutive
|
|
163
|
+
critical directions the nearest edge cannot change, so one ray at the mid-angle
|
|
164
|
+
finds it. The isovist boundary on that interval is then the exact intersection
|
|
165
|
+
of the two bounding rays with that edge's line, or a circular arc of radius
|
|
166
|
+
`max_radius`. Edges face the viewer only when the viewpoint lies on their open
|
|
167
|
+
side, so back faces are culled. Only arcs are discretised, and only for the
|
|
168
|
+
output polygon (`arc_step`). Area, perimeter, centroid and radial statistics use
|
|
169
|
+
exact sector formulas.
|
|
170
|
+
|
|
171
|
+
**Measures.** With `A` the area, `P` the perimeter and `r(θ)` the radial length:
|
|
172
|
+
|
|
173
|
+
| Measure | Definition | Source |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| area, perimeter | `A`, `P` | Benedikt 1979 |
|
|
176
|
+
| occlusivity | total length of the radial occluding edges (boundary that is not a surface). View-cone sides and view-distance arcs are not counted. | Benedikt 1979 |
|
|
177
|
+
| compactness | `4πA / P²` (1 for a circle) | circularity form used in isovist studies after Benedikt 1979; Batty 2001 |
|
|
178
|
+
| jaggedness | `P² / A` (4π for a circle) | Wiener et al. 2007 |
|
|
179
|
+
| drift, drift_angle | distance and direction from the viewpoint to the isovist centroid | Turner et al. 2001; Batty 2001 |
|
|
180
|
+
| min/max/mean/std radial | moments of `r(θ)` over the visible angle, as continuous integrals | Benedikt 1979 (radial moments); Batty 2001 |
|
|
181
|
+
|
|
182
|
+
For a straight piece at perpendicular distance `h`, with tangential coordinate
|
|
183
|
+
`τ`: `∫ r dθ = h ln((r₁+τ₁)/(r₀+τ₀))` and `∫ r² dθ = h (τ₁ − τ₀)`.
|
|
184
|
+
|
|
185
|
+
**VGA** (Turner et al. 2001). Nodes are grid points in free space. Two nodes
|
|
186
|
+
are linked when they can see each other (both ways, by the same exact rule as
|
|
187
|
+
`is_visible`). For each node:
|
|
188
|
+
|
|
189
|
+
- *connectivity* is the number of linked nodes.
|
|
190
|
+
- *clustering coefficient* is the number of links among the node's neighbours
|
|
191
|
+
divided by `k(k−1)/2` (Watts & Strogatz 1998).
|
|
192
|
+
- *mean depth* is `MD = Σ depth / (k−1)` over the node's connected component of
|
|
193
|
+
`k` nodes, from breadth-first search.
|
|
194
|
+
- *relative asymmetry* is `RA = 2(MD−1)/(k−2)`.
|
|
195
|
+
- *integration* is `D_k / RA`, with `D_k = 2{k[log₂((k+2)/3) − 1] + 1} / ((k−1)(k−2))`
|
|
196
|
+
(Hillier & Hanson 1984).
|
|
197
|
+
|
|
198
|
+
Each row of the visibility matrix is computed from one exact isovist, so VGA
|
|
199
|
+
costs one isovist per grid point plus an `n × n` BFS in matrix form.
|
|
200
|
+
|
|
201
|
+
### References
|
|
202
|
+
|
|
203
|
+
- Batty, M. (2001). Exploring isovist fields: space and shape in architectural and urban morphology. *Environment and Planning B*, 28(1), 123–150. https://doi.org/10.1068/b2725
|
|
204
|
+
- Benedikt, M. L. (1979). To take hold of space: isovists and isovist fields. *Environment and Planning B*, 6(1), 47–65. https://doi.org/10.1068/b060047
|
|
205
|
+
- Hillier, B., & Hanson, J. (1984). *The Social Logic of Space*. Cambridge University Press.
|
|
206
|
+
- Turner, A., Doxa, M., O'Sullivan, D., & Penn, A. (2001). From isovists to visibility graphs: a methodology for the analysis of architectural space. *Environment and Planning B*, 28(1), 103–121. https://doi.org/10.1068/b2684
|
|
207
|
+
- Watts, D. J., & Strogatz, S. H. (1998). Collective dynamics of "small-world" networks. *Nature*, 393, 440–442. https://doi.org/10.1038/30918
|
|
208
|
+
- Wiener, J. M., Franz, G., Rossmanith, N., Reichelt, A., Mallot, H. A., & Bülthoff, H. H. (2007). Isovist analysis captures properties of space relevant for locomotion and experience. *Perception*, 36(7), 1066–1083. https://doi.org/10.1068/p5587
|
|
209
|
+
|
|
210
|
+
## Limitations
|
|
211
|
+
|
|
212
|
+
- 2D only: no terrain, no building heights, no eye level. Every obstacle blocks
|
|
213
|
+
the view completely.
|
|
214
|
+
- Planar coordinates only. Reproject geographic data first.
|
|
215
|
+
- Isovist cost grows with (front-facing edges)², so a single isovist among a
|
|
216
|
+
few thousand edges takes milliseconds to tenths of a second. VGA needs one
|
|
217
|
+
isovist per grid point plus an `n × n` matrix: a few thousand points is
|
|
218
|
+
practical, but tens of thousands is not. Use a coarser grid, a sub-area or
|
|
219
|
+
`max_radius`.
|
|
220
|
+
- Without a boundary, every viewpoint needs `max_radius` (open-space views
|
|
221
|
+
never close).
|
|
222
|
+
- Measure names differ between tools such as depthmapX. Check the definitions
|
|
223
|
+
above before you compare numbers. Integration is computed per connected
|
|
224
|
+
component; nodes that see every other node have `RA = 0` and integration `inf`
|
|
225
|
+
(written as `null`).
|
|
226
|
+
- Geometric tolerance defaults to `1e-9 ×` the extent of the data. Pass
|
|
227
|
+
`Environment(tol=...)` for unusual data.
|
|
228
|
+
|
|
229
|
+
## Development
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
pip install -e ".[dev]"
|
|
233
|
+
python -m pytest tests -q
|
|
234
|
+
python -m ruff check . && python -m ruff format --check src tests
|
|
235
|
+
python -m mypy src
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The tests check results against hand-computed geometries: a convex room, a room
|
|
239
|
+
with a pillar, an L-shaped corridor, view cones, view-distance circles clipped
|
|
240
|
+
by walls and small graphs with known depths. They also cross-check against
|
|
241
|
+
independent sampled rays.
|
|
242
|
+
|
|
243
|
+
## Academic citation
|
|
244
|
+
|
|
245
|
+
If you use `isovist` in research, please cite it (see also `CITATION.cff`):
|
|
246
|
+
|
|
247
|
+
```bibtex
|
|
248
|
+
@software{eminoglu_isovist,
|
|
249
|
+
author = {Eminoğlu, Yusuf},
|
|
250
|
+
title = {isovist},
|
|
251
|
+
year = {2026},
|
|
252
|
+
version = {0.1.0},
|
|
253
|
+
url = {https://github.com/YusufEminoglu/isovist_sdk}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
MIT © 2026 Yusuf Eminoğlu. See [LICENSE](LICENSE).
|
isovist-0.1.0/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# isovist
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/isovist/)
|
|
4
|
+
[](https://pypi.org/project/isovist/)
|
|
5
|
+
[](https://github.com/YusufEminoglu/isovist_sdk/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/YusufEminoglu/isovist_sdk/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
**Exact 2D isovists and visibility graph analysis for urban design.**
|
|
9
|
+
|
|
10
|
+
`isovist` computes what can be seen from a point in a plan of buildings, walls
|
|
11
|
+
and columns, and describes the shape of that visible space with the classic
|
|
12
|
+
isovist measures. It also runs visibility graph analysis (VGA) on a grid of
|
|
13
|
+
points and answers point-to-point "can A see B?" questions. The only dependency
|
|
14
|
+
is NumPy.
|
|
15
|
+
|
|
16
|
+
- **Exact isovist polygon** from an angular sweep with exact ray/edge
|
|
17
|
+
intersections, not a fan of sampled rays. Optional maximum view distance and
|
|
18
|
+
field of view (angle and direction).
|
|
19
|
+
- **Isovist measures**: area, perimeter, compactness, jaggedness, occlusivity,
|
|
20
|
+
number of occluding edges, drift (distance and direction) and min/max/mean/std
|
|
21
|
+
radial length. All of them come from closed-form integrals.
|
|
22
|
+
- **Intervisibility**: exact; a sight line may graze a corner or run along a
|
|
23
|
+
wall, but it may not cross an obstacle.
|
|
24
|
+
- **Visibility graph analysis**: connectivity, clustering coefficient, mean
|
|
25
|
+
depth, relative asymmetry and integration on grids or on your own points.
|
|
26
|
+
An optional visual radius limits the links.
|
|
27
|
+
- **GeoJSON in/out** in projected coordinates, plus an `isovist` command-line tool.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install isovist
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Requires Python 3.9 or newer.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Quick start (Python)
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from isovist import Environment
|
|
41
|
+
|
|
42
|
+
room = [(0, 0), (10, 0), (10, 10), (0, 10)]
|
|
43
|
+
pillar = [(4, 4), (6, 4), (6, 6), (4, 6)]
|
|
44
|
+
env = Environment(obstacles=[pillar], boundary=room)
|
|
45
|
+
|
|
46
|
+
iso = env.isovist((2, 5))
|
|
47
|
+
iso.area # 70.0 (100 - pillar 4 - shadow 26)
|
|
48
|
+
iso.occlusivity # 13.416 (two occluding edges of length sqrt(45))
|
|
49
|
+
iso.polygon # [(x, y), ...] counter-clockwise ring
|
|
50
|
+
|
|
51
|
+
m = iso.measures()
|
|
52
|
+
m.compactness, m.drift, m.mean_radial, m.max_radial
|
|
53
|
+
|
|
54
|
+
env.isovist((2, 5), fov=90, direction=0, max_radius=6) # 90 deg view cone, 6 m reach
|
|
55
|
+
env.is_visible((3, 5), (7, 5)) # False: the pillar is in the way
|
|
56
|
+
|
|
57
|
+
from isovist import visibility_graph_analysis
|
|
58
|
+
vga = visibility_graph_analysis(env, spacing=1.0)
|
|
59
|
+
vga.connectivity, vga.clustering, vga.mean_depth, vga.integration
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Obstacles and the boundary are `isovist.Polygon` objects (holes allowed) or plain
|
|
63
|
+
vertex lists. Obstacles may overlap each other and may stick out of the boundary.
|
|
64
|
+
Holes in the boundary act as obstacles. The viewpoint must lie strictly in free
|
|
65
|
+
space.
|
|
66
|
+
|
|
67
|
+
## Quick start (command line)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# isovists with all measures for two viewpoints and for every Point in a file
|
|
71
|
+
isovist compute --obstacles buildings.geojson --boundary site.geojson \
|
|
72
|
+
--at 512340.5,4512200 --points viewpoints.geojson -o isovists.geojson
|
|
73
|
+
|
|
74
|
+
# 120 degree view cone towards north-east, 80 m reach
|
|
75
|
+
isovist compute --obstacles buildings.geojson --at 512340.5,4512200 \
|
|
76
|
+
--fov 120 --direction 45 --max-radius 80
|
|
77
|
+
|
|
78
|
+
# VGA on a 2 m grid (writes grid points with the measures)
|
|
79
|
+
isovist vga --obstacles buildings.geojson --boundary site.geojson --spacing 2 -o vga.geojson
|
|
80
|
+
|
|
81
|
+
# intervisibility; exit code 0 = visible, 1 = not visible
|
|
82
|
+
isovist visible --obstacles buildings.geojson --from 10,20 --to 80,45
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Write negative coordinates as `--at=-12.5,40` so they are not read as options.
|
|
86
|
+
Without `--boundary`, open-space isovists need `--max-radius`.
|
|
87
|
+
|
|
88
|
+
See `examples/quickstart.py` for a small plaza with two pavilions and a column.
|
|
89
|
+
|
|
90
|
+
## Coordinates and GeoJSON
|
|
91
|
+
|
|
92
|
+
All geometry is planar, so coordinates must be **projected** (metres or feet),
|
|
93
|
+
for example UTM. RFC 7946 GeoJSON is nominally longitude/latitude. `isovist`
|
|
94
|
+
reads and writes the same structure with projected coordinates, as produced by
|
|
95
|
+
`ogr2ogr -t_srs EPSG:32635 -lco RFC7946=NO out.geojson in.gpkg`. A legacy
|
|
96
|
+
`crs` member in the input is copied to the output. `load_environment` warns when
|
|
97
|
+
the coordinates look like degrees.
|
|
98
|
+
|
|
99
|
+
Outputs: `isovist compute` writes Polygon features with the viewpoint
|
|
100
|
+
(`vp_x`, `vp_y`), the input point properties and every measure. `isovist vga`
|
|
101
|
+
writes Point features with `connectivity`, `clustering`, `mean_depth`,
|
|
102
|
+
`relative_asymmetry`, `integration` and `node_count`. Undefined values
|
|
103
|
+
(`nan`, `inf`) become `null`.
|
|
104
|
+
|
|
105
|
+
## API overview
|
|
106
|
+
|
|
107
|
+
| Object / function | Purpose |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `Environment(obstacles, boundary=None, tol=None)` | the plan; `.isovist()`, `.is_visible()`, `.visibility_matrix()`, `.locate()` |
|
|
110
|
+
| `Environment.isovist(vp, max_radius=None, fov=None, direction=0, arc_step=1)` | exact `Isovist` |
|
|
111
|
+
| `Isovist` | `.polygon`, `.area`, `.perimeter`, `.centroid`, `.occlusivity`, `.occluding_edges`, `.radial(theta)`, `.contains(pt)`, `.measures()`, `.to_geojson()` |
|
|
112
|
+
| `IsovistMeasures` | area, perimeter, compactness, jaggedness, occlusivity, occluding_edges, drift, drift_angle, min/max/mean/std_radial |
|
|
113
|
+
| `visibility_graph_analysis(env, points=None, spacing=None, max_radius=None)` | `VGAResult` with per-node arrays and `.records()` |
|
|
114
|
+
| `grid_points(env, spacing, bounds=None, origin=None)` | cell-centre grid in free space |
|
|
115
|
+
| `graph_measures(adjacency)` | VGA measures for any undirected graph |
|
|
116
|
+
| `load_environment`, `polygons_from_geojson`, `points_from_geojson`, `feature_collection`, `write_geojson` | GeoJSON I/O |
|
|
117
|
+
|
|
118
|
+
Angles are in degrees, counted counter-clockwise from east (+x), in both the
|
|
119
|
+
API and the CLI. `Isovist.radial` takes radians.
|
|
120
|
+
|
|
121
|
+
## Method and sources
|
|
122
|
+
|
|
123
|
+
**Isovist.** An isovist is the set of points visible from a vantage point
|
|
124
|
+
(Benedikt 1979). Every edge end point, every crossing between overlapping
|
|
125
|
+
polygons, every point where an edge meets the view-distance circle and each
|
|
126
|
+
field-of-view limit gives a *critical direction*. Between two consecutive
|
|
127
|
+
critical directions the nearest edge cannot change, so one ray at the mid-angle
|
|
128
|
+
finds it. The isovist boundary on that interval is then the exact intersection
|
|
129
|
+
of the two bounding rays with that edge's line, or a circular arc of radius
|
|
130
|
+
`max_radius`. Edges face the viewer only when the viewpoint lies on their open
|
|
131
|
+
side, so back faces are culled. Only arcs are discretised, and only for the
|
|
132
|
+
output polygon (`arc_step`). Area, perimeter, centroid and radial statistics use
|
|
133
|
+
exact sector formulas.
|
|
134
|
+
|
|
135
|
+
**Measures.** With `A` the area, `P` the perimeter and `r(θ)` the radial length:
|
|
136
|
+
|
|
137
|
+
| Measure | Definition | Source |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| area, perimeter | `A`, `P` | Benedikt 1979 |
|
|
140
|
+
| occlusivity | total length of the radial occluding edges (boundary that is not a surface). View-cone sides and view-distance arcs are not counted. | Benedikt 1979 |
|
|
141
|
+
| compactness | `4πA / P²` (1 for a circle) | circularity form used in isovist studies after Benedikt 1979; Batty 2001 |
|
|
142
|
+
| jaggedness | `P² / A` (4π for a circle) | Wiener et al. 2007 |
|
|
143
|
+
| drift, drift_angle | distance and direction from the viewpoint to the isovist centroid | Turner et al. 2001; Batty 2001 |
|
|
144
|
+
| min/max/mean/std radial | moments of `r(θ)` over the visible angle, as continuous integrals | Benedikt 1979 (radial moments); Batty 2001 |
|
|
145
|
+
|
|
146
|
+
For a straight piece at perpendicular distance `h`, with tangential coordinate
|
|
147
|
+
`τ`: `∫ r dθ = h ln((r₁+τ₁)/(r₀+τ₀))` and `∫ r² dθ = h (τ₁ − τ₀)`.
|
|
148
|
+
|
|
149
|
+
**VGA** (Turner et al. 2001). Nodes are grid points in free space. Two nodes
|
|
150
|
+
are linked when they can see each other (both ways, by the same exact rule as
|
|
151
|
+
`is_visible`). For each node:
|
|
152
|
+
|
|
153
|
+
- *connectivity* is the number of linked nodes.
|
|
154
|
+
- *clustering coefficient* is the number of links among the node's neighbours
|
|
155
|
+
divided by `k(k−1)/2` (Watts & Strogatz 1998).
|
|
156
|
+
- *mean depth* is `MD = Σ depth / (k−1)` over the node's connected component of
|
|
157
|
+
`k` nodes, from breadth-first search.
|
|
158
|
+
- *relative asymmetry* is `RA = 2(MD−1)/(k−2)`.
|
|
159
|
+
- *integration* is `D_k / RA`, with `D_k = 2{k[log₂((k+2)/3) − 1] + 1} / ((k−1)(k−2))`
|
|
160
|
+
(Hillier & Hanson 1984).
|
|
161
|
+
|
|
162
|
+
Each row of the visibility matrix is computed from one exact isovist, so VGA
|
|
163
|
+
costs one isovist per grid point plus an `n × n` BFS in matrix form.
|
|
164
|
+
|
|
165
|
+
### References
|
|
166
|
+
|
|
167
|
+
- Batty, M. (2001). Exploring isovist fields: space and shape in architectural and urban morphology. *Environment and Planning B*, 28(1), 123–150. https://doi.org/10.1068/b2725
|
|
168
|
+
- Benedikt, M. L. (1979). To take hold of space: isovists and isovist fields. *Environment and Planning B*, 6(1), 47–65. https://doi.org/10.1068/b060047
|
|
169
|
+
- Hillier, B., & Hanson, J. (1984). *The Social Logic of Space*. Cambridge University Press.
|
|
170
|
+
- Turner, A., Doxa, M., O'Sullivan, D., & Penn, A. (2001). From isovists to visibility graphs: a methodology for the analysis of architectural space. *Environment and Planning B*, 28(1), 103–121. https://doi.org/10.1068/b2684
|
|
171
|
+
- Watts, D. J., & Strogatz, S. H. (1998). Collective dynamics of "small-world" networks. *Nature*, 393, 440–442. https://doi.org/10.1038/30918
|
|
172
|
+
- Wiener, J. M., Franz, G., Rossmanith, N., Reichelt, A., Mallot, H. A., & Bülthoff, H. H. (2007). Isovist analysis captures properties of space relevant for locomotion and experience. *Perception*, 36(7), 1066–1083. https://doi.org/10.1068/p5587
|
|
173
|
+
|
|
174
|
+
## Limitations
|
|
175
|
+
|
|
176
|
+
- 2D only: no terrain, no building heights, no eye level. Every obstacle blocks
|
|
177
|
+
the view completely.
|
|
178
|
+
- Planar coordinates only. Reproject geographic data first.
|
|
179
|
+
- Isovist cost grows with (front-facing edges)², so a single isovist among a
|
|
180
|
+
few thousand edges takes milliseconds to tenths of a second. VGA needs one
|
|
181
|
+
isovist per grid point plus an `n × n` matrix: a few thousand points is
|
|
182
|
+
practical, but tens of thousands is not. Use a coarser grid, a sub-area or
|
|
183
|
+
`max_radius`.
|
|
184
|
+
- Without a boundary, every viewpoint needs `max_radius` (open-space views
|
|
185
|
+
never close).
|
|
186
|
+
- Measure names differ between tools such as depthmapX. Check the definitions
|
|
187
|
+
above before you compare numbers. Integration is computed per connected
|
|
188
|
+
component; nodes that see every other node have `RA = 0` and integration `inf`
|
|
189
|
+
(written as `null`).
|
|
190
|
+
- Geometric tolerance defaults to `1e-9 ×` the extent of the data. Pass
|
|
191
|
+
`Environment(tol=...)` for unusual data.
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
pip install -e ".[dev]"
|
|
197
|
+
python -m pytest tests -q
|
|
198
|
+
python -m ruff check . && python -m ruff format --check src tests
|
|
199
|
+
python -m mypy src
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The tests check results against hand-computed geometries: a convex room, a room
|
|
203
|
+
with a pillar, an L-shaped corridor, view cones, view-distance circles clipped
|
|
204
|
+
by walls and small graphs with known depths. They also cross-check against
|
|
205
|
+
independent sampled rays.
|
|
206
|
+
|
|
207
|
+
## Academic citation
|
|
208
|
+
|
|
209
|
+
If you use `isovist` in research, please cite it (see also `CITATION.cff`):
|
|
210
|
+
|
|
211
|
+
```bibtex
|
|
212
|
+
@software{eminoglu_isovist,
|
|
213
|
+
author = {Eminoğlu, Yusuf},
|
|
214
|
+
title = {isovist},
|
|
215
|
+
year = {2026},
|
|
216
|
+
version = {0.1.0},
|
|
217
|
+
url = {https://github.com/YusufEminoglu/isovist_sdk}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT © 2026 Yusuf Eminoğlu. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "FeatureCollection",
|
|
3
|
+
"features": [
|
|
4
|
+
{
|
|
5
|
+
"type": "Feature",
|
|
6
|
+
"properties": {
|
|
7
|
+
"name": "pavilion west"
|
|
8
|
+
},
|
|
9
|
+
"geometry": {
|
|
10
|
+
"type": "Polygon",
|
|
11
|
+
"coordinates": [
|
|
12
|
+
[
|
|
13
|
+
[
|
|
14
|
+
15,
|
|
15
|
+
8
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
27,
|
|
19
|
+
8
|
|
20
|
+
],
|
|
21
|
+
[
|
|
22
|
+
27,
|
|
23
|
+
16
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
15,
|
|
27
|
+
16
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
15,
|
|
31
|
+
8
|
|
32
|
+
]
|
|
33
|
+
]
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"type": "Feature",
|
|
39
|
+
"properties": {
|
|
40
|
+
"name": "pavilion east"
|
|
41
|
+
},
|
|
42
|
+
"geometry": {
|
|
43
|
+
"type": "Polygon",
|
|
44
|
+
"coordinates": [
|
|
45
|
+
[
|
|
46
|
+
[
|
|
47
|
+
34,
|
|
48
|
+
24
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
46,
|
|
52
|
+
24
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
46,
|
|
56
|
+
33
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
34,
|
|
60
|
+
33
|
|
61
|
+
],
|
|
62
|
+
[
|
|
63
|
+
34,
|
|
64
|
+
24
|
|
65
|
+
]
|
|
66
|
+
]
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"type": "Feature",
|
|
72
|
+
"properties": {
|
|
73
|
+
"name": "column"
|
|
74
|
+
},
|
|
75
|
+
"geometry": {
|
|
76
|
+
"type": "Polygon",
|
|
77
|
+
"coordinates": [
|
|
78
|
+
[
|
|
79
|
+
[
|
|
80
|
+
30,
|
|
81
|
+
12
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
31,
|
|
85
|
+
12
|
|
86
|
+
],
|
|
87
|
+
[
|
|
88
|
+
31,
|
|
89
|
+
13
|
|
90
|
+
],
|
|
91
|
+
[
|
|
92
|
+
30,
|
|
93
|
+
13
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
30,
|
|
97
|
+
12
|
|
98
|
+
]
|
|
99
|
+
]
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|