#!/usr/bin/env python
# -*- coding: utf-8 -*-
# distribution.py
# definitons of spatial distribution characters
import statistics
import numpy as np
import pandas as pd
from shapely.geometry import LineString, Point
from tqdm import tqdm # progress bar
__all__ = [
"Orientation",
"SharedWallsRatio",
"StreetAlignment",
"CellAlignment",
"Alignment",
"NeighborDistance",
"MeanInterbuildingDistance",
"NeighboringStreetOrientationDeviation",
"BuildingAdjacency",
"Neighbors",
]
[docs]class Orientation:
"""
Calculate the orientation of object
Defined as an orientation of the longext axis of bounding rectangle in range 0 - 45.
It captures the deviation of orientation from cardinal directions.
Parameters
----------
gdf : GeoDataFrame
GeoDataFrame containing objects to analyse
Attributes
----------
series : Series
Series containing resulting values
gdf : GeoDataFrame
original GeoDataFrame
References
----------
Schirmer PM and Axhausen KW (2015) A multiscale classification of urban morphology.
Journal of Transport and Land Use 9(1): 101–130. (adapted)
Examples
--------
>>> buildings_df['orientation'] = momepy.Orientation(buildings_df).series
100%|██████████| 144/144 [00:00<00:00, 630.54it/s]
>>> buildings_df['orientation'][0]
41.05146788287027
"""
[docs] def __init__(self, gdf):
self.gdf = gdf
# define empty list for results
results_list = []
def _azimuth(point1, point2):
"""azimuth between 2 shapely points (interval 0 - 180)"""
angle = np.arctan2(point2.x - point1.x, point2.y - point1.y)
return np.degrees(angle) if angle > 0 else np.degrees(angle) + 180
# iterating over rows one by one
for index, row in tqdm(gdf.iterrows(), total=gdf.shape[0]):
bbox = list(row["geometry"].minimum_rotated_rectangle.exterior.coords)
centroid_ab = LineString([bbox[0], bbox[1]]).centroid
centroid_cd = LineString([bbox[2], bbox[3]]).centroid
axis1 = centroid_ab.distance(centroid_cd)
centroid_bc = LineString([bbox[1], bbox[2]]).centroid
centroid_da = LineString([bbox[3], bbox[0]]).centroid
axis2 = centroid_bc.distance(centroid_da)
if axis1 <= axis2:
az = _azimuth(centroid_bc, centroid_da)
if 90 > az >= 45:
diff = az - 45
az = az - 2 * diff
elif 135 > az >= 90:
diff = az - 90
az = az - 2 * diff
diff = az - 45
az = az - 2 * diff
elif 181 > az >= 135:
diff = az - 135
az = az - 2 * diff
diff = az - 90
az = az - 2 * diff
diff = az - 45
az = az - 2 * diff
results_list.append(az)
else:
az = 170
az = _azimuth(centroid_ab, centroid_cd)
if 90 > az >= 45:
diff = az - 45
az = az - 2 * diff
elif 135 > az >= 90:
diff = az - 90
az = az - 2 * diff
diff = az - 45
az = az - 2 * diff
elif 181 > az >= 135:
diff = az - 135
az = az - 2 * diff
diff = az - 90
az = az - 2 * diff
diff = az - 45
az = az - 2 * diff
results_list.append(az)
self.series = pd.Series(results_list, index=gdf.index)
[docs]class SharedWallsRatio:
"""
Calculate shared walls ratio of adjacent elements (typically buildings)
.. math::
\\textit{length of shared walls} \\over perimeter
Parameters
----------
gdf : GeoDataFrame
GeoDataFrame containing gdf to analyse
unique_id : str, list, np.array, pd.Series
the name of the dataframe column, np.array, or pd.Series with unique id
perimeters : str, list, np.array, pd.Series (default None)
the name of the dataframe column, np.array, or pd.Series where is stored perimeter value
Attributes
----------
series : Series
Series containing resulting values
gdf : GeoDataFrame
original GeoDataFrame
id : Series
Series containing used unique ID
perimeters : GeoDataFrame
Series containing used perimeters values
sindex : rtree spatial index
spatial index of gdf
References
----------
Hamaina R, Leduc T and Moreau G (2012) Towards Urban Fabrics Characterization
Based on Buildings Footprints. In: Lecture Notes in Geoinformation and Cartography,
Berlin, Heidelberg: Springer Berlin Heidelberg, pp. 327–346. Available from:
https://link.springer.com/chapter/10.1007/978-3-642-29063-3_18.
Examples
--------
>>> buildings_df['swr'] = momepy.SharedWallsRatio(buildings_df, 'uID').series
100%|██████████| 144/144 [00:00<00:00, 648.72it/s]
>>> buildings_df['swr'][10]
0.3424804411228673
"""
[docs] def __init__(self, gdf, unique_id, perimeters=None):
self.gdf = gdf
gdf = gdf.copy()
self.sindex = gdf.sindex # define rtree index
# define empty list for results
results_list = []
if perimeters is None:
gdf["mm_p"] = gdf.geometry.length
perimeters = "mm_p"
else:
if not isinstance(perimeters, str):
gdf["mm_p"] = perimeters
perimeters = "mm_p"
self.perimeters = gdf[perimeters]
if not isinstance(unique_id, str):
gdf["mm_uid"] = unique_id
unique_id = "mm_uid"
self.id = gdf[unique_id]
for i, (index, row) in tqdm(enumerate(gdf.iterrows()), total=gdf.shape[0]):
neighbors = list(self.sindex.intersection(row.geometry.bounds))
neighbors.remove(i)
# if no neighbour exists
length = 0
if not neighbors:
results_list.append(0)
else:
for i in neighbors:
subset = gdf.iloc[i]["geometry"]
length = length + row.geometry.intersection(subset).length
results_list.append(length / row[perimeters])
self.series = pd.Series(results_list, index=gdf.index)
[docs]class StreetAlignment:
"""
Calculate the difference between street orientation and orientation of object in degrees
Orientation of street segment is represented by the orientation of line
connecting first and last point of the segment. Network ID linking each object
to specific street segment is needed. Can be generated by :py:func:`momepy.get_network_id`.
Either network_id or left_network_id and right_network_id are required.
.. math::
\\left|{\\textit{building orientation} - \\textit{street orientation}}\\right|
Parameters
----------
left : GeoDataFrame
GeoDataFrame containing objects to analyse
right : GeoDataFrame
GeoDataFrame containing street network
orientations : str, list, np.array, pd.Series
the name of the dataframe column, np.array, or pd.Series where is stored object orientation value
(can be calculated using :py:func:`momepy.orientation`)
network_id : str (default None)
the name of the column storing network ID in both left and right
left_network_id : str, list, np.array, pd.Series (default None)
the name of the left dataframe column, np.array, or pd.Series where is stored object network ID
right_network_id : str, list, np.array, pd.Series (default None)
the name of the right dataframe column, np.array, or pd.Series of streets with unique network id (has to be defined beforehand)
(can be defined using :py:func:`momepy.elements.unique_id`)
Attributes
----------
series : Series
Series containing resulting values
left : GeoDataFrame
original left GeoDataFrame
right : GeoDataFrame
original right GeoDataFrame
network_id : str
the name of the column storing network ID in both left and right
left_network_id : Series
Series containing used left ID
right_network_id : Series
Series containing used right ID
Examples
--------
>>> buildings_df['street_alignment'] = momepy.StreetAlignment(buildings_df, streets_df, 'orientation', 'nID', 'nID').series
100%|██████████| 144/144 [00:00<00:00, 529.94it/s]
>>> buildings_df['street_alignment'][0]
0.29073888476702336
"""
[docs] def __init__(
self,
left,
right,
orientations,
network_id=None,
left_network_id=None,
right_network_id=None,
):
self.left = left
self.right = right
self.network_id = network_id
# define empty list for results
results_list = []
left = left.copy()
right = right.copy()
if network_id:
left_network_id = network_id
right_network_id = network_id
else:
if left_network_id is None and right_network_id is not None:
raise ValueError("left_network_id not set.")
if left_network_id is not None and right_network_id is None:
raise ValueError("right_network_id not set.")
if left_network_id is None and right_network_id is None:
raise ValueError(
"Network ID not set. Use either network_id or left_network_id and right_network_id."
)
if not isinstance(orientations, str):
left["mm_o"] = orientations
orientations = "mm_o"
self.orientations = left[orientations]
if not isinstance(left_network_id, str):
left["mm_nid"] = left_network_id
left_network_id = "mm_nid"
self.left_network_id = left[left_network_id]
if not isinstance(right_network_id, str):
right["mm_nis"] = right_network_id
right_network_id = "mm_nis"
self.right_network_id = right[right_network_id]
def _azimuth(point1, point2):
"""azimuth between 2 shapely points (interval 0 - 180)"""
angle = np.arctan2(point2.x - point1.x, point2.y - point1.y)
return np.degrees(angle) if angle > 0 else np.degrees(angle) + 180
# iterating over rows one by one
for index, row in tqdm(left.iterrows(), total=left.shape[0]):
if pd.isnull(row[left_network_id]):
results_list.append(0)
else:
network_id = row[left_network_id]
streetssub = right.loc[right[right_network_id] == network_id]
start = Point(streetssub.iloc[0]["geometry"].coords[0])
end = Point(streetssub.iloc[0]["geometry"].coords[-1])
az = _azimuth(start, end)
if 90 > az >= 45:
diff = az - 45
az = az - 2 * diff
elif 135 > az >= 90:
diff = az - 90
az = az - 2 * diff
diff = az - 45
az = az - 2 * diff
elif 181 > az >= 135:
diff = az - 135
az = az - 2 * diff
diff = az - 90
az = az - 2 * diff
diff = az - 45
az = az - 2 * diff
results_list.append(abs(row[orientations] - az))
self.series = pd.Series(results_list, index=left.index)
[docs]class CellAlignment:
"""
Calculate the difference between cell orientation and orientation of object
.. math::
\\left|{\\textit{building orientation} - \\textit{cell orientation}}\\right|
Parameters
----------
left : GeoDataFrame
GeoDataFrame containing objects to analyse
right : GeoDataFrame
GeoDataFrame containing tessellation cells (or relevant spatial units)
left_orientations : str, list, np.array, pd.Series
the name of the left dataframe column, np.array, or pd.Series where is stored object orientation value
(can be calculated using :py:func:`momepy.orientation`)
right_orientations : str, list, np.array, pd.Series
the name of the right dataframe column, np.array, or pd.Series where is stored object orientation value
(can be calculated using :py:func:`momepy.orientation`)
left_unique_id : str
the name of the left dataframe column with unique id shared between left and right gdf
right_unique_id : str
the name of the right dataframe column with unique id shared between left and right gdf
Attributes
----------
series : Series
Series containing resulting values
left : GeoDataFrame
original left GeoDataFrame
right : GeoDataFrame
original right GeoDataFrame
left_orientations : Series
Series containing used left orientations
right_orientations : Series
Series containing used right orientations
left_unique_id : Series
Series containing used left ID
right_unique_id : Series
Series containing used right ID
Examples
--------
>>> buildings_df['cell_alignment'] = momepy.CellAlignment(buildings_df, tessellation_df, 'bl_orient', 'tes_orient', 'uID', 'uID').series
100%|██████████| 144/144 [00:00<00:00, 799.09it/s]
>>> buildings_df['cell_alignment'][0]
0.8795123936951939
"""
[docs] def __init__(
self,
left,
right,
left_orientations,
right_orientations,
left_unique_id,
right_unique_id,
):
self.left = left
self.right = right
left = left.copy()
right = right.copy()
if not isinstance(left_orientations, str):
left["mm_o"] = left_orientations
left_orientations = "mm_o"
self.left_orientations = left[left_orientations]
if not isinstance(right_orientations, str):
right["mm_o"] = right_orientations
right_orientations = "mm_o"
self.right_orientations = right[right_orientations]
self.left_unique_id = left[left_unique_id]
self.right_unique_id = right[right_unique_id]
# define empty list for results
results_list = []
for index, row in tqdm(left.iterrows(), total=left.shape[0]):
results_list.append(
abs(
row[left_orientations]
- right[right[right_unique_id] == row[left_unique_id]][
right_orientations
].iloc[0]
)
)
self.series = pd.Series(results_list, index=left.index)
[docs]class Alignment:
"""
Calculate the mean deviation of solar orientation of objects on adjacent cells from an object
.. math::
\\frac{1}{n}\\sum_{i=1}^n dev_i=\\frac{dev_1+dev_2+\\cdots+dev_n}{n}
Parameters
----------
gdf : GeoDataFrame
GeoDataFrame containing objects to analyse
spatial_weights : libpysal.weights, optional
spatial weights matrix
orientations : str, list, np.array, pd.Series
the name of the left dataframe column, np.array, or pd.Series where is stored object orientation value
(can be calculated using :py:func:`momepy.orientation`)
unique_id : str
name of the column with unique id used as spatial_weights index.
Attributes
----------
series : Series
Series containing resulting values
gdf : GeoDataFrame
original GeoDataFrame
orientations : Series
Series containing used orientation values
sw : libpysal.weights
spatial weights matrix
id : Series
Series containing used unique ID
Examples
--------
>>> buildings_df['alignment'] = momepy.Alignment(buildings_df, sw, 'uID', bl_orient).series
100%|██████████| 144/144 [00:01<00:00, 140.84it/s]
>>> buildings_df['alignment'][0]
18.299481296455237
"""
[docs] def __init__(self, gdf, spatial_weights, unique_id, orientations):
self.gdf = gdf
self.sw = spatial_weights
self.id = gdf[unique_id]
# define empty list for results
results_list = []
gdf = gdf.copy()
if not isinstance(orientations, str):
gdf["mm_o"] = orientations
orientations = "mm_o"
self.orientations = gdf[orientations]
data = gdf.set_index(unique_id)
# iterating over rows one by one
for index, row in tqdm(data.iterrows(), total=data.shape[0]):
neighbours = spatial_weights.neighbors[index].copy()
if neighbours:
orientation = data.loc[neighbours][orientations]
deviations = abs(orientation - row[orientations])
results_list.append(statistics.mean(deviations))
else:
results_list.append(0)
self.series = pd.Series(results_list, index=gdf.index)
[docs]class NeighborDistance:
"""
Calculate the mean distance to adjacent buildings (based on spatial_weights)
.. math::
\\frac{1}{n}\\sum_{i=1}^n dist_i=\\frac{dist_1+dist_2+\\cdots+dist_n}{n}
Parameters
----------
gdf : GeoDataFrame
GeoDataFrame containing objects to analyse
spatial_weights : libpysal.weights
spatial weights matrix
unique_id : str
name of the column with unique id used as spatial_weights index.
Attributes
----------
series : Series
Series containing resulting values
gdf : GeoDataFrame
original GeoDataFrame
sw : libpysal.weights
spatial weights matrix
id : Series
Series containing used unique ID
References
----------
Schirmer PM and Axhausen KW (2015) A multiscale classification of urban morphology.
Journal of Transport and Land Use 9(1): 101–130. (adapted)
Examples
--------
>>> buildings_df['neighbour_distance'] = momepy.NeighborDistance(buildings_df, sw, 'uID').series
100%|██████████| 144/144 [00:00<00:00, 345.78it/s]
>>> buildings_df['neighbour_distance'][0]
29.18589019096464
"""
[docs] def __init__(self, gdf, spatial_weights, unique_id):
self.gdf = gdf
self.sw = spatial_weights
self.id = gdf[unique_id]
# define empty list for results
results_list = []
data = gdf.set_index(unique_id)
# iterating over rows one by one
for index, row in tqdm(data.iterrows(), total=data.shape[0]):
neighbours = spatial_weights.neighbors[index]
building_neighbours = data.loc[neighbours]
if len(building_neighbours) > 0:
results_list.append(
np.mean(building_neighbours.geometry.distance(row["geometry"]))
)
else:
results_list.append(0)
self.series = pd.Series(results_list, index=gdf.index)
[docs]class MeanInterbuildingDistance:
"""
Calculate the mean interbuilding distance
Interbuilding distances are calculated between buildings on adjacent cells based on `spatial_weights`,
while the extent is defined in `spatial_weights_higher`.
.. math::
Parameters
----------
gdf : GeoDataFrame
GeoDataFrame containing objects to analyse
unique_id : str
name of the column with unique id used as spatial_weights index
spatial_weights : libpysal.weights
spatial weights matrix
spatial_weights_higher : libpysal.weights, optional
spatial weights matrix - If None, Queen contiguity of higher order will be calculated
based on spatial_weights
order : int
Order of Queen contiguity
Attributes
----------
series : Series
Series containing resulting values
gdf : GeoDataFrame
original GeoDataFrame
sw : libpysal.weights
spatial weights matrix
id : Series
Series containing used unique ID
sw_higher : libpysal.weights
Spatial weights matrix of higher order
order : int
Order of Queen contiguity (only if spatial_weights_higher was not set)
Notes
-----
Fix UserWarning.
Examples
--------
>>> buildings_df['mean_interbuilding_distance'] = momepy.MeanInterbuildingDistance(buildings_df, sw, 'uID').series
Generating weights matrix (Queen) of 3 topological steps...
Generating adjacency matrix based on weights matrix...
Computing interbuilding distances...
100%|██████████| 746/746 [00:03<00:00, 200.14it/s]
Computing mean interbuilding distances...
100%|██████████| 144/144 [00:00<00:00, 317.42it/s]
>>> buildings_df['mean_interbuilding_distance'][0]
29.305457092042744
"""
[docs] def __init__(
self, gdf, spatial_weights, unique_id, spatial_weights_higher=None, order=3
):
self.gdf = gdf
self.sw = spatial_weights
self.id = gdf[unique_id]
if spatial_weights_higher is None:
print(
"Generating weights matrix (Queen) of {} topological steps...".format(
order
)
)
self.order = order
from momepy import sw_high
# matrix to define area of analysis (more steps)
spatial_weights_higher = sw_high(k=order, weights=spatial_weights)
self.sw_higher = spatial_weights_higher
# define empty list for results
results_list = []
print("Generating adjacency matrix based on weights matrix...")
# define adjacency list from lipysal
adj_list = spatial_weights.to_adjlist()
adj_list["distance"] = -1
print("Computing interbuilding distances...")
# measure each interbuilding distance of neighbours and save them to adjacency list
for index, row in tqdm(adj_list.iterrows(), total=adj_list.shape[0]):
inverted = adj_list[(adj_list.focal == row.neighbor)][
(adj_list.neighbor == row.focal)
].iloc[0]["distance"]
if inverted == -1:
building_object = gdf.loc[gdf[unique_id] == row.focal.astype(int)]
building_neighbour = gdf.loc[gdf[unique_id] == row.neighbor.astype(int)]
adj_list.loc[index, "distance"] = building_neighbour.iloc[
0
].geometry.distance(building_object.iloc[0].geometry)
else:
adj_list.at[index, "distance"] = inverted
print("Computing mean interbuilding distances...")
# iterate over objects to get the final values
for index, row in tqdm(gdf.iterrows(), total=gdf.shape[0]):
# id to match spatial weights
uid = row[unique_id]
# define neighbours based on weights matrix defining analysis area
neighbours = spatial_weights_higher.neighbors[uid].copy()
neighbours.append(uid)
if neighbours:
selection = adj_list[adj_list.focal.isin(neighbours)][
adj_list.neighbor.isin(neighbours)
]
results_list.append(np.nanmean(selection.distance))
self.series = pd.Series(results_list, index=gdf.index)
[docs]class NeighboringStreetOrientationDeviation:
"""
Calculate the mean deviation of solar orientation of adjacent streets
Orientation of street segment is represented by the orientation of line
connecting first and last point of the segment.
.. math::
\\frac{1}{n}\\sum_{i=1}^n dev_i=\\frac{dev_1+dev_2+\\cdots+dev_n}{n}
Parameters
----------
gdf : GeoDataFrame
GeoDataFrame containing street network to analyse
Attributes
----------
series : Series
Series containing resulting values
gdf : GeoDataFrame
original GeoDataFrame
orientation : Series
Series containing used street orientation values
sindex : rtree spatial index
spatial index of gdf
Examples
--------
>>> streets_df['orient_dev'] = momepy.NeighboringStreetOrientationDeviation(streets_df).series
Preparing street orientations...
Generating spatial index...
100%|██████████| 33/33 [00:00<00:00, 249.02it/s]
>>> streets_df['orient_dev'][6]
7.043096518688273
"""
[docs] def __init__(self, gdf):
self.gdf = gdf
results_list = []
gdf = gdf.copy()
def _azimuth(point1, point2):
"""azimuth between 2 shapely points (interval 0 - 180)"""
angle = np.arctan2(point2.x - point1.x, point2.y - point1.y)
return np.degrees(angle) if angle > 0 else np.degrees(angle) + 180
# iterating over rows one by one
print(" Preparing street orientations...")
for index, row in tqdm(gdf.iterrows(), total=gdf.shape[0]):
start = Point(row["geometry"].coords[0])
end = Point(row["geometry"].coords[-1])
az = _azimuth(start, end)
if 90 > az >= 45:
diff = az - 45
az = az - 2 * diff
elif 135 > az >= 90:
diff = az - 90
az = az - 2 * diff
diff = az - 45
az = az - 2 * diff
elif 181 > az >= 135:
diff = az - 135
az = az - 2 * diff
diff = az - 90
az = az - 2 * diff
diff = az - 45
az = az - 2 * diff
results_list.append(az)
self.orientation = pd.Series(results_list, index=gdf.index)
gdf["tmporient"] = self.orientation
print(" Generating spatial index...")
self.sindex = gdf.sindex
results_list = []
for index, row in tqdm(gdf.iterrows(), total=gdf.shape[0]):
possible_neighbors_idx = list(self.sindex.intersection(row.geometry.bounds))
possible_neighbours = gdf.iloc[possible_neighbors_idx]
neighbors = possible_neighbours[
possible_neighbours.intersects(row.geometry)
]
neighbors.drop([index])
orientations = []
for idx, r in neighbors.iterrows():
orientations.append(r.tmporient)
deviations = []
for o in orientations:
dev = abs(o - row.tmporient)
deviations.append(dev)
if deviations:
results_list.append(np.mean(deviations))
else:
results_list.append(0)
self.series = pd.Series(results_list, index=gdf.index)
[docs]class BuildingAdjacency:
"""
Calculate the level of building adjacency
Building adjacency reflects how much buildings tend to join together into larger structures.
It is calculated as a ratio of joined built-up structures and buildings within
the extent defined in `spatial_weights_higher`.
.. math::
Parameters
----------
gdf : GeoDataFrame
GeoDataFrame containing objects to analyse
spatial_weights_higher : libpysal.weights
spatial weights matrix
unique_id : str
name of the column with unique id used as spatial_weights index
spatial_weights : libpysal.weights, optional
spatial weights matrix - If None, Queen contiguity matrix will be calculated
based on gdf. It is to denote adjacent buildings (note: based on unique ID).
Attributes
----------
series : Series
Series containing resulting values
gdf : GeoDataFrame
original GeoDataFrame
sw_higher : libpysal.weights
spatial weights matrix
id : Series
Series containing used unique ID
sw : libpysal.weights
spatial weights matrix
References
----------
Vanderhaegen S and Canters F (2017) Mapping urban form and function at city
block level using spatial metrics. Landscape and Urban Planning 167: 399–409.
Examples
--------
>>> buildings_df['adjacency'] = momepy.BuildingAdjacency(buildings_df, swh, unique_id='uID').series
Calculating spatial weights...
Spatial weights ready...
100%|██████████| 144/144 [00:00<00:00, 9301.73it/s]
Calculating adjacency...
100%|██████████| 144/144 [00:00<00:00, 335.55it/s]
>>> buildings_df['adjacency'][10]
0.23809523809523808
"""
[docs] def __init__(self, gdf, spatial_weights_higher, unique_id, spatial_weights=None):
self.gdf = gdf
self.sw_higher = spatial_weights_higher
self.id = gdf[unique_id]
results_list = []
# if weights matrix is not passed, generate it from gdf
if spatial_weights is None:
print("Calculating spatial weights...")
from libpysal.weights import Queen
spatial_weights = Queen.from_dataframe(
gdf, silence_warnings=True, ids=unique_id
)
print("Spatial weights ready...")
self.sw = spatial_weights
patches = dict(zip(gdf[unique_id], spatial_weights.component_labels))
print("Calculating adjacency...")
for index, row in tqdm(gdf.iterrows(), total=gdf.shape[0]):
neighbours = spatial_weights_higher.neighbors[row[unique_id]].copy()
if neighbours:
neighbours.append(row[unique_id])
patches_sub = [patches[x] for x in neighbours]
patches_nr = len(set(patches_sub))
results_list.append(patches_nr / len(neighbours))
else:
results_list.append(0)
self.series = pd.Series(results_list, index=gdf.index)
[docs]class Neighbors:
"""
Calculate the number of neighbours captured by spatial_weights
If weighted=True, number of neighbours will be divided by the perimeter of object
to return relative value.
.. math::
Parameters
----------
gdf : GeoDataFrame
GeoDataFrame containing objects to analyse
spatial_weights : libpysal.weights
spatial weights matrix
unique_id : str
name of the column with unique id used as spatial_weights index
weighted : bool (default False)
if weighted=True, number of neighbours will be divided by the perimeter of object, to return relative value
Attributes
----------
series : Series
Series containing resulting values
gdf : GeoDataFrame
original GeoDataFrame
values : Series
Series containing used values
sw : libpysal.weights
spatial weights matrix
id : Series
Series containing used unique ID
weighted : bool
used weighted value
References
----------
Hermosilla T, Ruiz LA, Recio JA, et al. (2012) Assessing contextual descriptive features
for plot-based classification of urban areas. Landscape and Urban Planning, Elsevier B.V.
106(1): 124–137.
Examples
--------
>>> sw = libpysal.weights.contiguity.Queen.from_dataframe(tessellation_df, ids='uID')
>>> tessellation_df['neighbours'] = momepy.Neighbors(tessellation_df, sw, 'uID').series
100%|██████████| 144/144 [00:00<00:00, 6909.50it/s]
>>> tessellation_df['neighbours'][0]
4
"""
[docs] def __init__(self, gdf, spatial_weights, unique_id, weighted=False):
self.gdf = gdf
self.sw = spatial_weights
self.id = gdf[unique_id]
self.weighted = weighted
neighbours = []
for index, row in tqdm(gdf.iterrows(), total=gdf.shape[0]):
if weighted is True:
neighbours.append(
spatial_weights.cardinalities[row[unique_id]] / row.geometry.length
)
else:
neighbours.append(spatial_weights.cardinalities[row[unique_id]])
self.series = pd.Series(neighbours, index=gdf.index)