Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Snape #2

Merged
merged 9 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 7 additions & 57 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
fail_fast: true
fail_fast: false
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v3.17.0
rev: v3.19.0
hooks:
- id: pyupgrade
args:
Expand All @@ -20,13 +20,8 @@ repos:
- id: prettier
types: [yaml]

# - repo: https://github.com/abravalheri/validate-pyproject
# rev: v0.16
# hooks:
# - id: validate-pyproject

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.2
rev: v1.13.0
hooks:
- id: mypy
verbose: true
Expand Down Expand Up @@ -98,7 +93,7 @@ repos:
entry: check-executables-have-shebangs
language: python
types: [text, executable]
stages: [commit, push, manual]
stages: [pre-commit, pre-push, manual]
- id: check-json
name: check json
description: checks json files for parseable syntax.
Expand All @@ -111,7 +106,7 @@ repos:
entry: check-shebang-scripts-are-executable
language: python
types: [text]
stages: [commit, push, manual]
stages: [pre-commit, pre-push, manual]
- id: check-merge-conflict
name: check for merge conflicts
description: checks for files that contain merge conflict strings.
Expand Down Expand Up @@ -172,7 +167,7 @@ repos:
entry: end-of-file-fixer
language: python
types: [text]
stages: [commit, push, manual]
stages: [pre-commit, pre-push, manual]
- id: file-contents-sorter
name: file contents sorter
description: sorts the lines in specified files (defaults to alphabetical). you must provide list of target files as input in your .pre-commit-config.yaml file.
Expand Down Expand Up @@ -215,49 +210,4 @@ repos:
entry: trailing-whitespace-fixer
language: python
types: [text]
stages: [commit, push, manual]

# - repo: local
# hooks:
# - id: sphinx-check
# name: sphinx-check
# entry: docs/make.sh
# language: script
# types: [ python ]
# pass_filenames: false

# - repo: local
# hooks:
# - id: pytest-check
# name: pytest-check
# entry: pytest migration --ignore-glob=*exclude*
# language: system
# pass_filenames: false
# always_run: true

# - repo: https://github.com/jshwi/docsig # BUGGY!
# rev: v0.30.0
# hooks:
# - id: docsig
# args:
# - "--check-class"
# - "--check-dunders"
# - "--check-overridden"
# - "--check-protected"
# - "--summary"

#- repo: https://github.com/pycqa/pydocstyle # SLOW!
# rev: 6.3.0 # pick a git hash / tag to point to
# hooks:
# - id: pydocstyle

# - repo: https://github.com/jsh9/pydoclint
# rev: 0.3.8
# hooks:
# - id: pydoclint
# args:
# [
# --style=sphinx,
# --check-return-types=False,
# --arg-type-hints-in-docstring=False,
# ]
stages: [pre-commit, pre-push, manual]
2 changes: 1 addition & 1 deletion jord/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

__project__ = "Jord"
__author__ = "Christian Heider Lindbjerg"
__version__ = "0.8.7"
__version__ = "0.8.8"
__doc__ = r"""
.. module:: jord
:platform: Unix, Windows
Expand Down
1 change: 1 addition & 0 deletions jord/geometric_analysis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
from .center_line import *
from .intersections import *
from .principal_axis import *
from .simple_center_line import *
50 changes: 25 additions & 25 deletions jord/geometric_analysis/center_line.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
from typing import Union, Tuple, List, Iterable
from typing import Iterable, List, Tuple, Union

import numpy
from numpy import array
Expand Down Expand Up @@ -40,28 +40,28 @@ def find_centerline(
int(min(ext_xy[1])),
)

vertices, ridges = _get_voronoi_vertices_and_ridges(
vertices, ridges = get_voronoi_vertices_and_ridges(
input_geometry, step_size, minx=_min_x, miny=_min_y
)

lines = []
for ridge in ridges:
if _ridge_is_finite(ridge):
starting_point = _create_point_with_restored_coordinates(
if ridge_is_finite(ridge):
starting_point = create_point_with_restored_coordinates(
x=vertices[ridge[0]][0],
y=vertices[ridge[0]][1],
_min_x=_min_x,
_min_y=_min_y,
)
ending_point = _create_point_with_restored_coordinates(
ending_point = create_point_with_restored_coordinates(
x=vertices[ridge[1]][0],
y=vertices[ridge[1]][1],
_min_x=_min_x,
_min_y=_min_y,
)
linestring = LineString((starting_point, ending_point))

if _linestring_is_within_input_geometry(linestring, input_geometry):
if linestring_is_within_input_geometry(linestring, input_geometry):
lines.append(linestring)

if len(lines) < 2:
Expand All @@ -70,94 +70,94 @@ def find_centerline(
return MultiLineString(lines=unary_union(lines))


def _get_voronoi_vertices_and_ridges(
def get_voronoi_vertices_and_ridges(
_input_geometry: BaseGeometry,
_step_size: float,
minx: float,
miny: float,
) -> Tuple[numpy.ndarray, List[List[int]]]:
borders = _get_densified_borders(_input_geometry, _step_size, minx, miny)
borders = densify_border(_input_geometry, _step_size, minx, miny)

voronoi_diagram = Voronoi(borders)
return voronoi_diagram.vertices, voronoi_diagram.ridge_vertices


def _ridge_is_finite(ridge: Iterable) -> bool:
def ridge_is_finite(ridge: Iterable) -> bool:
return -1 not in ridge


def _create_point_with_restored_coordinates(
def create_point_with_restored_coordinates(
x: float, y: float, _min_x: float, _min_y: float
) -> Tuple[float, float]:
return (x + _min_x, y + _min_y)


def _linestring_is_within_input_geometry(
def linestring_is_within_input_geometry(
linestring: LineString, input_geometry: BaseGeometry
) -> bool:
return linestring.within(input_geometry) and len(linestring.coords[0]) > 1


def _get_densified_borders(
def densify_border(
_input_geometry: BaseGeometry, _step_size, minx: float, miny: float
) -> numpy.ndarray:
polygons = iter_polygons(_input_geometry)
points = []
for polygon in polygons:
points += _get_interpolated_boundary(polygon.exterior, _step_size, minx, miny)
points += interpolated_boundary(polygon.exterior, _step_size, minx, miny)
if polygon_has_interior_rings(polygon):
for interior in polygon.interiors:
points += _get_interpolated_boundary(
points += interpolated_boundary(
interior, _step_size, minx=minx, miny=miny
)

return array(points)


def _get_interpolated_boundary(
def interpolated_boundary(
boundary: BaseGeometry, _step_size: float, minx: float, miny: float
) -> List[Tuple[float, float]]:
line = LineString(boundary)

return (
[_get_coordinates_of_first_point(line, minx, miny)]
+ _get_coordinates_of_interpolated_points(
[get_coordinates_of_first_point(line, minx, miny)]
+ get_coordinates_of_interpolated_points(
line, _step_size, min_x=minx, min_y=miny
)
+ [_get_coordinates_of_last_point(line, minx=minx, miny=miny)]
+ [get_coordinates_of_last_point(line, minx=minx, miny=miny)]
)


def _create_point_with_reduced_coordinates(
def create_point_with_reduced_coordinates(
x: float, y: float, _min_x: float, _min_y: float
) -> Tuple[float, float]:
return (x - _min_x, y - _min_y)


def _get_coordinates_of_first_point(
def get_coordinates_of_first_point(
linestring: LineString, minx: float, miny: float
) -> Tuple[float, float]:
return _create_point_with_reduced_coordinates(
return create_point_with_reduced_coordinates(
x=linestring.xy[0][0], y=linestring.xy[1][0], _min_x=minx, _min_y=miny
)


def _get_coordinates_of_last_point(
def get_coordinates_of_last_point(
linestring: LineString, minx: float, miny: float
) -> Tuple[float, float]:
return _create_point_with_reduced_coordinates(
return create_point_with_reduced_coordinates(
x=linestring.xy[0][-1], y=linestring.xy[1][-1], _min_x=minx, _min_y=miny
)


def _get_coordinates_of_interpolated_points(
def get_coordinates_of_interpolated_points(
linestring: LineString, _step_size: Number, min_x: float, min_y: float
) -> List[Tuple[float, float]]:
interpolation_distance = _step_size
intermediate_points = []
while interpolation_distance < linestring.length:
point = linestring.interpolate(interpolation_distance)
reduced_point = _create_point_with_reduced_coordinates(
reduced_point = create_point_with_reduced_coordinates(
x=point.x, y=point.y, _min_x=min_x, _min_y=min_y
)
intermediate_points.append(reduced_point)
Expand Down
3 changes: 1 addition & 2 deletions jord/geometric_analysis/principal_axis.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from typing import Collection, Tuple, Union

import shapely
from geojson import LineString
from shapely import affinity

__all__ = [
Expand All @@ -27,7 +26,7 @@ class PrincipalityMeasure(Enum):

def other_mass_projection_is_longer(
poly: shapely.geometry.base.BaseGeometry,
first_axis: LineString,
first_axis: shapely.LineString,
other_axis: shapely.LineString,
hybrid_normalised_axes_projections: bool = False,
) -> bool:
Expand Down
61 changes: 61 additions & 0 deletions jord/geometric_analysis/simple_center_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from typing import Optional, Union

import geopandas
import momepy
import shapely

__all__ = ["construct_centerline"]


def construct_centerline(
input_geometry: shapely.Polygon,
interpolation_distance: Optional[float] = None,
truncate_endings: bool = True,
merge_lines: bool = True,
simplify_lines: bool = False,
) -> Union[shapely.LineString, shapely.MultiLineString]:
if interpolation_distance is None:
interpolation_distance = input_geometry.minimum_clearance

if interpolation_distance < 1e-15:
if isinstance(input_geometry, shapely.MultiPolygon):
return shapely.MultiLineString([f.exterior for f in input_geometry.geoms])
return input_geometry.exterior

densified_border = input_geometry.segmentize(interpolation_distance * 0.9)

voronoi_polys = shapely.voronoi_polygons(
densified_border,
only_edges=True,
) # equivalent to the scipy.spatial.Voronoi

center_lines = geopandas.GeoDataFrame(
geometry=geopandas.GeoSeries(voronoi_polys.geoms)
).sjoin(
geopandas.GeoDataFrame(geometry=geopandas.GeoSeries(input_geometry)),
predicate="within",
) # to select only the linestring within the input geometry

if truncate_endings:
graph = momepy.gdf_to_nx(center_lines)

graph.remove_nodes_from(
node for node, degree in dict(graph.degree()).items() if degree < 2
)

center_lines = momepy.nx_to_gdf(graph, points=False)

ret = center_lines.unary_union

if merge_lines:
ret = shapely.line_merge(ret)

if simplify_lines:
ret = shapely.simplify(ret, tolerance=interpolation_distance * 0.1)

if isinstance(ret, shapely.geometry.MultiLineString):
geoms = list(ret.geoms)
if len(geoms) == 1:
return geoms[0]

return ret
13 changes: 9 additions & 4 deletions jord/geopandas_utilities/serialisation/well_known_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,26 @@

from pathlib import Path

import pandas
import shapely.geometry.base
from shapely import wkb

__all__ = ["load_wkbs_from_csv", "csv_wkt_generator"]


def load_wkbs_from_csv(csv_file_path: Path, geometry_column: str = "Shape") -> wkb:
def load_wkbs_from_csv(
csv_file_path: Path, geometry_column: str = "Shape"
) -> pandas.DataFrame:
"""
Well-Known Text
"""
import pandas

return pandas.read_csv(str(csv_file_path))[geometry_column].apply(wkb.loads)


def csv_wkt_generator(csv_file_path: Path, geometry_column: str = "Shape") -> wkb:
def csv_wkt_generator(
csv_file_path: Path, geometry_column: str = "Shape"
) -> shapely.geometry.base.BaseGeometry:
"""
:param csv_file_path:
Expand All @@ -28,4 +33,4 @@ def csv_wkt_generator(csv_file_path: Path, geometry_column: str = "Shape") -> wk
for idx, g in pandas.read_csv(
str(csv_file_path), usecols=[geometry_column]
).iterrows():
yield wkb.loads(g)
yield wkb.loads(g) # g is pandas Series?
Loading
Loading