Skip to content
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
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Changes - from version >= 1.x
=============================

2025-12-21
----------

**version 1.2.1**

* [enhancement] `Blocks` can handle missing geometries (geometry as `None` or `NaN`)
* [debug] `Blocks` default value column is `block_values`, and `PointSupport` default value column is `values` (to avoiding naming collisions during the join operation)
* [debug] `Blocks` object default index is created when it is not provided by the user

2025-11-23
----------

Expand Down
20 changes: 13 additions & 7 deletions src/pyinterpolate/core/data_models/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
import geopandas as gpd
import numpy as np
import pandas as pd

from shapely.geometry import Polygon
from shapely import MultiPolygon

from pyinterpolate.distance.angular import calc_angles
from pyinterpolate.distance.point import point_distance
Expand Down Expand Up @@ -219,10 +218,17 @@ def __init__(self,
self.ds = ds.copy(deep=True)
else:
if value_column_name is None:
value_column_name = 'values'
self.ds = join_any_geometry_and_values(geometry=geometries,
values=values,
values_column_name=value_column_name)
value_column_name = 'block_value'
self.ds = join_any_geometry_and_values(
geometry=geometries,
values=values,
values_column_name=value_column_name
)
if index_column_name is None:
index_column_name = 'block_index'

self.ds.index.name = index_column_name
self.ds.reset_index(inplace=True)

self.value_column_name = value_column_name
self.index_column_name = index_column_name
Expand Down Expand Up @@ -716,6 +722,6 @@ def _sample_larges_geometries(geometries: gpd.GeoSeries) -> gpd.GeoSeries:
present.
"""
ds = geometries.apply(
lambda x: x if isinstance(x, Polygon) else largest_geometry(x)
lambda x: largest_geometry(x) if isinstance(x, MultiPolygon) else x
)
return ds
4 changes: 2 additions & 2 deletions src/pyinterpolate/transform/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ def points_to_lon_lat(points: gpd.GeoSeries) -> Tuple:
Longitude, latitude (x, y)
"""

lon = points.apply(lambda pt: pt.x)
lat = points.apply(lambda pt: pt.y)
lon = points.apply(lambda pt: pt.x if isinstance(pt, Point) else None)
lat = points.apply(lambda pt: pt.y if isinstance(pt, Point) else None)
return lon, lat


Expand Down
254 changes: 254 additions & 0 deletions tests/test_core/test_point_support_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import geopandas as gpd

from shapely.geometry import Point, Polygon

from pyinterpolate.core.data_models.blocks import Blocks
from pyinterpolate.core.data_models.point_support import PointSupport
from .sample_data.dataprep import CANCER_DATA_WITH_CENTROIDS, POINT_SUPPORT_DATA
Expand Down Expand Up @@ -115,3 +117,255 @@ def test_unique_blocks():
)

assert len(ps.unique_blocks) == len(blocks.ds.index)


def test_missing_block_values():
block_values = [10, np.nan, 11]

block_geoms = [
Polygon(
[(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]
),
Polygon(
[(10, 0), (20, 0), (20, 10), (10, 10), (10, 0)]
),
Polygon(
[(10, 10), (20, 10), (20, 20), (10, 20), (10, 10)]
)
]

blocks = Blocks(
values=block_values,
geometries=block_geoms
)

points_values = [1, 2, 1, 3, 1, 5, 1, 6, 7, 7]
points_geoms = [
Point(2, 2),
Point(3, 3),
Point(4, 4),
Point(15, 15),
Point(16, 16),
Point(17, 17),
Point(18, 8),
Point(19, 9),
Point(10, 10),
Point(12, 12)
]

point_support = PointSupport(
blocks=blocks,
values=points_values,
geometries=points_geoms
)

assert isinstance(point_support, PointSupport)
assert isinstance(point_support.point_support, gpd.GeoDataFrame)

points = point_support.get_points_array()
assert isinstance(points, np.ndarray)

indexes = point_support.get_point_to_block_indexes()
assert isinstance(indexes, pd.Series)
assert (len(indexes) == len(points))


def test_missing_block_geometries_none():
block_values = [10, 10, 11]

block_geoms = [
Polygon(
[(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]
),
None,
Polygon(
[(10, 10), (20, 10), (20, 20), (10, 20), (10, 10)]
)
]

blocks = Blocks(
values=block_values,
geometries=block_geoms
)

points_values = [1, 2, 1, 3, 1, 5, 1, 6, 7, 7]
points_geoms = [
Point(2, 2),
Point(3, 3),
Point(4, 4),
Point(15, 15),
Point(16, 16),
Point(17, 17),
Point(18, 8),
Point(19, 9),
Point(10, 10),
Point(12, 12)
]

point_support = PointSupport(
blocks=blocks,
values=points_values,
geometries=points_geoms
)

assert isinstance(point_support, PointSupport)
assert isinstance(point_support.point_support, gpd.GeoDataFrame)

points = point_support.get_points_array()
assert isinstance(points, np.ndarray)

indexes = point_support.get_point_to_block_indexes()
assert isinstance(indexes, pd.Series)
assert (len(indexes) == len(points))


def test_missing_block_geometries_nan():
block_values = [10, 10, 11]

block_geoms = [
Polygon(
[(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]
),
np.nan,
Polygon(
[(10, 10), (20, 10), (20, 20), (10, 20), (10, 10)]
)
]

blocks = Blocks(
values=block_values,
geometries=block_geoms
)

points_values = [1, 2, 1, 3, 1, 5, 1, 6, 7, 7]
points_geoms = [
Point(2, 2),
Point(3, 3),
Point(4, 4),
Point(15, 15),
Point(16, 16),
Point(17, 17),
Point(18, 8),
Point(19, 9),
Point(10, 10),
Point(12, 12)
]

point_support = PointSupport(
blocks=blocks,
values=points_values,
geometries=points_geoms
)

assert isinstance(point_support, PointSupport)
assert isinstance(point_support.point_support, gpd.GeoDataFrame)

points = point_support.get_points_array()
assert isinstance(points, np.ndarray)

indexes = point_support.get_point_to_block_indexes()
assert isinstance(indexes, pd.Series)
assert (len(indexes) == len(points))


def test_missing_point_support_values():
block_values = [10, 9, 11]

block_geoms = [
Polygon(
[(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]
),
Polygon(
[(10, 0), (20, 0), (20, 10), (10, 10), (10, 0)]
),
Polygon(
[(10, 10), (20, 10), (20, 20), (10, 20), (10, 10)]
)
]

blocks = Blocks(
values=block_values,
geometries=block_geoms
)

points_values = [1, 2, 1, np.nan, 1, 5, None, 6, 7, 7]
points_geoms = [
Point(2, 2),
Point(3, 3),
Point(4, 4),
Point(15, 15),
Point(16, 16),
Point(17, 17),
Point(18, 8),
Point(19, 9),
Point(10, 10),
Point(12, 12)
]

point_support = PointSupport(
blocks=blocks,
values=points_values,
geometries=points_geoms
)

assert isinstance(point_support, PointSupport)
assert isinstance(point_support.point_support, gpd.GeoDataFrame)

points = point_support.get_points_array()
assert isinstance(points, np.ndarray)

indexes = point_support.get_point_to_block_indexes()
assert isinstance(indexes, pd.Series)
assert (len(indexes) == len(points))


def test_missing_point_support_geometries():
block_values = [10, 9, 11]

block_geoms = [
Polygon(
[(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]
),
Polygon(
[(10, 0), (20, 0), (20, 10), (10, 10), (10, 0)]
),
Polygon(
[(10, 10), (20, 10), (20, 20), (10, 20), (10, 10)]
)
]

blocks = Blocks(
values=block_values,
geometries=block_geoms
)

points_values = [1, 2, 1, 3, 1, 5, 1, 6, 7, 7]
points_geoms = [
Point(2, 2),
Point(3, 3),
Point(4, 4),
None,
Point(16, 16),
Point(17, 17),
np.nan,
Point(19, 9),
Point(10, 10),
Point(12, 12)
]

point_support = PointSupport(
blocks=blocks,
values=points_values,
geometries=points_geoms
)

assert isinstance(point_support, PointSupport)
assert isinstance(point_support.point_support, gpd.GeoDataFrame)

points = point_support.get_points_array()
assert isinstance(points, np.ndarray)

indexes = point_support.get_point_to_block_indexes()
assert isinstance(indexes, pd.Series)
assert (len(indexes) == len(points))

Loading