Skip to content

Commit

Permalink
ENH: support MultiPolygons (#234)
Browse files Browse the repository at this point in the history
* ENH: support MultiPolygons

* use dev mapclassify in CI properly
  • Loading branch information
martinfleis authored Dec 21, 2020
1 parent d296118 commit a1a5060
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 76 deletions.
3 changes: 2 additions & 1 deletion ci/envs/dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ dependencies:
- pytest
- pytest-cov
- codecov
- mapclassify
- pip
- osmnx
- black=19.10b0
- nose
- inequality
- pygeos
- dask
# for mapclassify
- scikit-learn
- pip:
- git+https://github.com/geopandas/geopandas.git
- git+https://github.com/pysal/libpysal.git
Expand Down
3 changes: 2 additions & 1 deletion momepy/intensity.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ class BlocksCount:
"""
Calculates the weighted number of blocks
Number of blocks within neighbours defined in ``spatial_weights``.
Number of blocks within neighbours defined in ``spatial_weights`` divided by the area
you have covered by the neighbours.
.. math::
Expand Down
16 changes: 8 additions & 8 deletions momepy/preprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def remove_false_nodes(gdf):
Returns
-------
gdf : GeoDataFrame, GeoSeries
See also
--------
momepy.extend_lines
Expand Down Expand Up @@ -343,7 +343,7 @@ class CheckTessellationInput:
have to be fixed prior Tessellation. Features which will split will cause issues
only sometimes, so
should be checked and fixed if necessary. Features which will collapse could
be ignored, but they will have to excluded from next steps of
be ignored, but they will have to excluded from next steps of
tessellation-based analysis.
Parameters
Expand Down Expand Up @@ -570,7 +570,7 @@ def close_gaps(gdf, tolerance):
Returns
-------
GeoSeries
See also
--------
momepy.extend_lines
Expand Down Expand Up @@ -609,7 +609,7 @@ def close_gaps(gdf, tolerance):


def extend_lines(gdf, tolerance, target=None, barrier=None, extension=0):
""" Extends lines from gdf to istelf or target within a set tolerance
"""Extends lines from gdf to istelf or target within a set tolerance
Extends unjoined ends of LineString segments to join with other segments or
target. If ``target`` is passed, extend lines to target. Otherwise extend
Expand All @@ -619,7 +619,7 @@ def extend_lines(gdf, tolerance, target=None, barrier=None, extension=0):
with ``barrier``. If they intersect, extended line is not returned. This
can be useful if you don't want to extend street network segments through
buildings.
Parameters
----------
gdf : GeoDataFrame
Expand All @@ -628,19 +628,19 @@ def extend_lines(gdf, tolerance, target=None, barrier=None, extension=0):
tolerance in snapping (by how much could be each segment
extended).
target : GeoDataFrame, GeoSeries
target geometry to which ``gdf`` gets extended. Has to be
target geometry to which ``gdf`` gets extended. Has to be
(Multi)LineString geometry.
barrier : GeoDataFrame, GeoSeries
extended line is not used if it intersects barrier
extension : float
by how much to extend line beyond the snapped geometry. Useful
when creating enclosures to avoid floating point imprecision.
Returns
-------
GeoDataFrame
GeoDataFrame of with extended geometry
See also
--------
momepy.close_gaps
Expand Down
173 changes: 107 additions & 66 deletions momepy/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ class CircularCompactness:
gdf : GeoDataFrame
GeoDataFrame containing objects
areas : str, list, np.array, pd.Series (default None)
the name of the dataframe column, ``np.array``, or ``pd.Series`` where is
the name of the dataframe column, ``np.array``, or ``pd.Series`` where is
stored area value. If set to ``None``, function will calculate areas
during the process without saving them separately.
Expand Down Expand Up @@ -727,7 +727,7 @@ class Corners:
"""
Calculates number of corners of each object in given GeoDataFrame.
Uses only external shape (``shapely.geometry.exterior``), courtyards are not
Uses only external shape (``shapely.geometry.exterior``), courtyards are not
included.
.. math::
Expand Down Expand Up @@ -781,33 +781,65 @@ def _true_angle(a, b, c):

# fill new column with the value of area, iterating over rows one by one
for geom in tqdm(gdf.geometry, total=gdf.shape[0], disable=not verbose):
corners = 0 # define empty variables
points = list(geom.exterior.coords) # get points of a shape
stop = len(points) - 1 # define where to stop
for i in np.arange(
len(points)
): # for every point, calculate angle and add 1 if True angle
if i == 0:
continue
elif i == stop:
a = np.asarray(points[i - 1])
b = np.asarray(points[i])
c = np.asarray(points[1])

if _true_angle(a, b, c) is True:
corners = corners + 1
else:
if geom.type == "Polygon":
corners = 0 # define empty variables
points = list(geom.exterior.coords) # get points of a shape
stop = len(points) - 1 # define where to stop
for i in np.arange(
len(points)
): # for every point, calculate angle and add 1 if True angle
if i == 0:
continue
elif i == stop:
a = np.asarray(points[i - 1])
b = np.asarray(points[i])
c = np.asarray(points[1])

else:
a = np.asarray(points[i - 1])
b = np.asarray(points[i])
c = np.asarray(points[i + 1])
if _true_angle(a, b, c) is True:
corners = corners + 1
else:
continue

if _true_angle(a, b, c) is True:
corners = corners + 1
else:
continue
a = np.asarray(points[i - 1])
b = np.asarray(points[i])
c = np.asarray(points[i + 1])

if _true_angle(a, b, c) is True:
corners = corners + 1
else:
continue
elif geom.type == "MultiPolygon":
corners = 0 # define empty variables
for g in geom.geoms:
points = list(g.exterior.coords) # get points of a shape
stop = len(points) - 1 # define where to stop
for i in np.arange(
len(points)
): # for every point, calculate angle and add 1 if True angle
if i == 0:
continue
elif i == stop:
a = np.asarray(points[i - 1])
b = np.asarray(points[i])
c = np.asarray(points[1])

if _true_angle(a, b, c) is True:
corners = corners + 1
else:
continue

else:
a = np.asarray(points[i - 1])
b = np.asarray(points[i])
c = np.asarray(points[i + 1])

if _true_angle(a, b, c) is True:
corners = corners + 1
else:
continue
else:
corners = np.nan

results_list.append(corners)

Expand All @@ -827,6 +859,8 @@ class Squareness:
Adapted from :cite:`dibble2017`.
Returns ``np.nan`` for MultiPolygons.
Parameters
----------
gdf : GeoDataFrame
Expand Down Expand Up @@ -1037,6 +1071,8 @@ class CentroidCorners:
Adapted from :cite:`schirmer2015` and :cite:`cimburova2017`.
Returns ``np.nan`` for MultiPolygons.
Parameters
----------
gdf : GeoDataFrame
Expand Down Expand Up @@ -1087,52 +1123,57 @@ def true_angle(a, b, c):

# iterating over rows one by one
for geom in tqdm(gdf.geometry, total=gdf.shape[0], disable=not verbose):
distances = [] # set empty list of distances
centroid = geom.centroid # define centroid
points = list(geom.exterior.coords) # get points of a shape
stop = len(points) - 1 # define where to stop
for i in np.arange(
len(points)
): # for every point, calculate angle and add 1 if True angle
if i == 0:
continue
elif i == stop:
a = np.asarray(points[i - 1])
b = np.asarray(points[i])
c = np.asarray(points[1])
p = Point(points[i])

if true_angle(a, b, c) is True:
distance = centroid.distance(
p
) # calculate distance point - centroid
distances.append(distance) # add distance to the list
else:
if geom.type == "Polygon":
distances = [] # set empty list of distances
centroid = geom.centroid # define centroid
points = list(geom.exterior.coords) # get points of a shape
stop = len(points) - 1 # define where to stop
for i in np.arange(
len(points)
): # for every point, calculate angle and add 1 if True angle
if i == 0:
continue
elif i == stop:
a = np.asarray(points[i - 1])
b = np.asarray(points[i])
c = np.asarray(points[1])
p = Point(points[i])

if true_angle(a, b, c) is True:
distance = centroid.distance(
p
) # calculate distance point - centroid
distances.append(distance) # add distance to the list
else:
continue

else:
a = np.asarray(points[i - 1])
b = np.asarray(points[i])
c = np.asarray(points[i + 1])
p = Point(points[i])

if true_angle(a, b, c) is True:
distance = centroid.distance(p)
distances.append(distance)
else:
continue
if not distances: # circular buildings
if geom.has_z:
coords = [
(coo[0], coo[1]) for coo in geom.convex_hull.exterior.coords
]
a = np.asarray(points[i - 1])
b = np.asarray(points[i])
c = np.asarray(points[i + 1])
p = Point(points[i])

if true_angle(a, b, c) is True:
distance = centroid.distance(p)
distances.append(distance)
else:
continue
if not distances: # circular buildings
if geom.has_z:
coords = [
(coo[0], coo[1]) for coo in geom.convex_hull.exterior.coords
]
else:
coords = geom.convex_hull.exterior.coords
results_list.append(_circle_radius(coords))
results_list_sd.append(0)
else:
coords = geom.convex_hull.exterior.coords
results_list.append(_circle_radius(coords))
results_list_sd.append(0)
results_list.append(np.mean(distances)) # calculate mean
results_list_sd.append(np.std(distances)) # calculate st.dev
else:
results_list.append(np.mean(distances)) # calculate mean
results_list_sd.append(np.std(distances)) # calculate st.dev
results_list.append(np.nan)
results_list_sd.append(np.nan)

self.mean = pd.Series(results_list, index=gdf.index)
self.std = pd.Series(results_list_sd, index=gdf.index)

Expand Down

0 comments on commit a1a5060

Please sign in to comment.