Skip to content

Commit e5a7ba2

Browse files
authored
Geopandas (#22)
* Speed up performance with spatial indexing. * Isort precommit hook * Performance. * Use geopandas for internal calculations. * Fix regex test.
1 parent 4220dde commit e5a7ba2

15 files changed

+2412
-1277
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66
doc/_build/*
77
dist/*
88
_build/*
9-
*.coverage
9+
*.coverage
10+
__pycache__/*
11+
.pytest_cache/*
12+
htmlcov/*

.pre-commit-config.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Pre-commit configuration for geo-adjacency project
2+
repos:
3+
# isort - Python import sorting
4+
- repo: https://github.com/pycqa/isort
5+
rev: 5.13.2
6+
hooks:
7+
- id: isort
8+
args: ["--profile", "black", "--line-length", "88"]
9+
10+
# Basic code quality checks
11+
- repo: https://github.com/pre-commit/pre-commit-hooks
12+
rev: v4.5.0
13+
hooks:
14+
- id: trailing-whitespace
15+
- id: end-of-file-fixer
16+
- id: check-yaml
17+
- id: check-added-large-files
18+
- id: check-merge-conflict
19+
- id: debug-statements
20+
- id: check-docstring-first
21+
22+
# Python-specific checks
23+
- repo: https://github.com/psf/black
24+
rev: 23.12.1
25+
hooks:
26+
- id: black
27+
language_version: python3
28+
args: ["--line-length", "88"]
29+
30+
# Remove unused imports
31+
- repo: https://github.com/PyCQA/autoflake
32+
rev: v2.2.1
33+
hooks:
34+
- id: autoflake
35+
args:
36+
- --in-place
37+
- --remove-all-unused-imports
38+
- --remove-unused-variables
39+
- --expand-star-imports
40+
- --ignore-init-module-imports

Example/example.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,31 @@
22
A series of example analyses.
33
"""
44

5+
import json
6+
import os.path
7+
58
from matplotlib import pyplot as plt
69
from scipy.spatial import voronoi_plot_2d
10+
from shapely.geometry import shape
711
from shapely.wkt import loads
812

913
from geo_adjacency.adjacency import AdjacencyEngine
10-
import os.path
11-
import json
12-
from shapely.geometry import shape
13-
1414

1515
##############
1616
# Data setup #
1717
##############
1818

19+
1920
def load_geojson(path):
2021
with open(path) as f:
2122
return [shape(feature["geometry"]) for feature in json.load(f)["features"]]
2223

24+
2325
# Load the example data
24-
data_dir = os.path.join(os.path.dirname(__file__), 'data')
25-
source_path = os.path.join(data_dir, 'Buildings.geojson')
26-
target_path = os.path.join(data_dir, 'Parks.geojson')
27-
obstacle_path = os.path.join(data_dir, 'Roads.geojson')
26+
data_dir = os.path.join(os.path.dirname(__file__), "data")
27+
source_path = os.path.join(data_dir, "Buildings.geojson")
28+
target_path = os.path.join(data_dir, "Parks.geojson")
29+
obstacle_path = os.path.join(data_dir, "Roads.geojson")
2830

2931
source_geoms = load_geojson(source_path)
3032
target_geoms = load_geojson(target_path)
@@ -55,7 +57,9 @@ def load_geojson(path):
5557
########################
5658
# Bounding box example #
5759
########################
58-
engine = AdjacencyEngine(source_geoms, **{"bounding_box": (-122.33872, 47.645, -122.33391, 47.65)})
60+
engine = AdjacencyEngine(
61+
source_geoms, **{"bounding_box": (-122.33872, 47.645, -122.33391, 47.65)}
62+
)
5963
engine.plot_adjacency_dict()
6064

6165

@@ -69,10 +73,11 @@ def load_geojson(path):
6973
# A 10x1 wkt 2 units away from the source
7074
obstacle_geoms = [loads("POLYGON ((2 0, 2 10, 3 10, 3 0, 2 0))")]
7175

72-
engine = AdjacencyEngine(source_geoms, target_geoms, obstacle_geoms, densify_features=True)
76+
engine = AdjacencyEngine(
77+
source_geoms, target_geoms, obstacle_geoms, densify_features=True
78+
)
7379
engine.plot_adjacency_dict()
7480

7581

76-
77-
if __name__ == '__main__':
78-
pass
82+
if __name__ == "__main__":
83+
pass

Example/geopandas_example.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Example demonstrating AdjacencyEngine with GeoPandas DataFrames.
4+
5+
This example shows how to use AdjacencyEngine with GeoPandas DataFrames
6+
to take advantage of vectorized operations and preserve attribute data.
7+
"""
8+
9+
import geopandas as gpd
10+
from shapely import Point, Polygon
11+
from geo_adjacency import AdjacencyEngine
12+
13+
14+
def main():
15+
"""Demonstrate GeoPandas functionality with AdjacencyEngine."""
16+
print("🌍 GeoPandas AdjacencyEngine Example")
17+
print("=" * 40)
18+
19+
# Create sample data with attributes
20+
# Points representing buildings
21+
buildings_gdf = gpd.GeoDataFrame({
22+
'building_id': ['B001', 'B002', 'B003', 'B004', 'B005'],
23+
'type': ['residential', 'commercial', 'industrial', 'residential', 'commercial'],
24+
'floors': [2, 10, 1, 3, 8],
25+
'year_built': [1995, 2010, 1980, 2000, 2015],
26+
'geometry': [
27+
Point(1, 1), Point(3, 1), Point(1, 3),
28+
Point(3, 3), Point(5, 2)
29+
]
30+
})
31+
32+
# Polygons representing parks
33+
parks_gdf = gpd.GeoDataFrame({
34+
'park_id': ['P001', 'P002'],
35+
'name': ['Central Park', 'River Park'],
36+
'area_hectares': [2.5, 1.8],
37+
'established': [1985, 1992],
38+
'geometry': [
39+
Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]),
40+
Polygon([(2.5, 2.5), (4.5, 2.5), (4.5, 4.5), (2.5, 4.5)])
41+
]
42+
})
43+
44+
print("📊 Input Data:")
45+
print(f"Buildings: {len(buildings_gdf)} features")
46+
print(f"Parks: {len(parks_gdf)} features")
47+
48+
# Create AdjacencyEngine with GeoDataFrames
49+
engine = AdjacencyEngine(buildings_gdf, parks_gdf)
50+
51+
# Get adjacency as traditional dictionary
52+
adjacency_dict = engine.get_adjacency_dict()
53+
print(f"\n🔗 Adjacency Results:")
54+
print(f"Total adjacency relationships: {sum(len(v) for v in adjacency_dict.values())}")
55+
56+
# Get adjacency as rich GeoDataFrame with attributes
57+
adjacency_gdf = engine.get_adjacency_gdf()
58+
59+
if adjacency_gdf is not None:
60+
print(f"\n📋 Adjacency GeoDataFrame:")
61+
print(f"Rows: {len(adjacency_gdf)}")
62+
print(f"Columns: {list(adjacency_gdf.columns)}")
63+
64+
print("\n🏢 Building-Park Adjacencies:")
65+
for _, row in adjacency_gdf.iterrows():
66+
building = row['source_building_id']
67+
building_type = row['source_type']
68+
park = row['target_park_id']
69+
park_name = row['target_name']
70+
print(f" {building} ({building_type}) ↔ {park} ({park_name})")
71+
72+
print("\n💡 Benefits of GeoPandas Integration:")
73+
print(" • Attribute data automatically preserved")
74+
print(" • Vectorized spatial operations for performance")
75+
print(" • Rich output format suitable for analysis")
76+
print(" • Seamless integration with pandas/geopandas workflows")
77+
else:
78+
print(" No adjacency relationships found")
79+
80+
# Demonstrate backward compatibility with lists
81+
print(f"\n🔄 Backward Compatibility:")
82+
building_geoms = list(buildings_gdf.geometry)
83+
park_geoms = list(parks_gdf.geometry)
84+
85+
engine_legacy = AdjacencyEngine(building_geoms, park_geoms)
86+
legacy_result = engine_legacy.get_adjacency_dict()
87+
88+
print(f"Legacy interface result: {sum(len(v) for v in legacy_result.values())} adjacencies")
89+
print("✓ Same results with both interfaces")
90+
91+
92+
if __name__ == "__main__":
93+
main()

doc/conf.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,25 @@
99
# -- Project information -----------------------------------------------------
1010
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
1111

12-
project = 'geo-adjacency'
13-
copyright = '2023, Andrew Smyth'
14-
author = 'Andrew Smyth'
15-
release = '1.2.0'
12+
project = "geo-adjacency"
13+
copyright = "2023, Andrew Smyth"
14+
author = "Andrew Smyth"
15+
release = "1.2.0"
1616

1717
# -- General configuration ---------------------------------------------------
1818
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
1919

2020
extensions = ["sphinx.ext.autodoc", "sphinx_sitemap", "sphinx.ext.napoleon"]
2121

22-
templates_path = ['_templates']
23-
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
22+
templates_path = ["_templates"]
23+
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
2424

25-
sys.path.insert(0, os.path.abspath('..'))
25+
sys.path.insert(0, os.path.abspath(".."))
2626
autodoc_mock_imports = ["scipy", "shapely", "numpy", "matplotlib"]
2727

2828
# -- Options for HTML output -------------------------------------------------
2929
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
3030

31-
html_theme = 'sphinx_rtd_theme'
32-
html_static_path = ['_static']
33-
html_baseurl = 'https://asmyth01.github.io/geo-adjacency/'
31+
html_theme = "sphinx_rtd_theme"
32+
html_static_path = ["_static"]
33+
html_baseurl = "https://asmyth01.github.io/geo-adjacency/"

geo_adjacency/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""
2+
Geo-adjacency: Spatial adjacency analysis using Voronoi diagrams.
3+
4+
This package provides tools for determining adjacency relationships between
5+
geometric features, even when they don't directly touch. It uses Voronoi
6+
diagram analysis to identify spatial relationships that account for gaps
7+
and obstacles between features.
8+
9+
Main class:
10+
AdjacencyEngine: The primary class for performing adjacency analysis.
11+
12+
Example:
13+
>>> from geo_adjacency.adjacency import AdjacencyEngine
14+
>>> from shapely.geometry import Point
15+
>>> sources = [Point(0, 0), Point(1, 0)]
16+
>>> targets = [Point(0, 1), Point(1, 1)]
17+
>>> engine = AdjacencyEngine(sources, targets)
18+
>>> adjacencies = engine.get_adjacency_dict()
19+
"""

0 commit comments

Comments
 (0)