"Momepy is a library for quantitative analysis of urban form - urban morphometrics. It is part of PySAL (Python Spatial Analysis Library) and is built on top of GeoPandas, other PySAL modules and networkX." momepy documentation
Using code (relevant portion below) from Fleischmann, Martin, Alessandra Feliciotti, and William Kerr. “Evolution of Urban Patterns: Urban Morphology as an Open Reproducible Data Science.” Geographical Analysis, July 15, 2021, gean.12302. https://doi.org/10.1111/gean.12302.
I am using OSM data to examine the morphometric character of a four small cities in central Illinois, USA. For a reason I cannot understand, I am encountering problems with the calculation of morphological tessellation cells in only one of the cities. I have included a link to an image of the tessellation cells below (I was not allowed to embed a photo).
Thank you for any assistance!
%%capture --no-stdout
for i, coords in cases.origin.iteritems():
s = time()
"""
donwload and process elements of urban form
"""
# download buildings from OSM
point = tuple(map(float, coords[1:-1].split(', ')))[::-1]
buildings = osmnx.geometries.geometries_from_point(point, dist=1000, tags={'building':True})
buildings = osmnx.projection.project_gdf(buildings)
buildings = buildings[buildings.geom_type.isin(['Polygon', 'MultiPolygon'])]
buildings = buildings.reset_index(drop=True).explode().reset_index(drop=True)
buildings = buildings[["geometry"]]
# download streets from OSM
streets_graph = osmnx.graph_from_point(point, 1250, network_type='drive')
streets_graph = osmnx.get_undirected(streets_graph)
streets_graph = osmnx.projection.project_graph(streets_graph)
streets = osmnx.graph_to_gdfs(streets_graph, nodes=False, edges=True,
node_geometry=False, fill_edge_geometry=True)
streets = streets[["geometry"]]
# check CRS
assert buildings.crs.equals(streets.crs)
# generate tessellation
buildings['uID'] = range(len(buildings))
tessellation = mm.Tessellation(buildings, "uID", limit=box(*buildings.total_bounds), verbose=False).tessellation
"""
measure morphometric characters
"""
# cell area
tessellation['cell_area'] = tessellation.area
# building area
buildings['blg_area'] = buildings.area
# coverage area ratio
tessellation["car"] = mm.AreaRatio(tessellation, buildings, "cell_area", buildings.area, "uID").series
# segment length
streets["length"] = streets.length
# segment linearity
streets["linearity"] = mm.Linearity(streets, verbose=False).series
# street profile
profile = mm.StreetProfile(streets, buildings, distance=3)
streets["width"] = profile.w
streets["width_deviation"] = profile.wd
streets["openness"] = profile.o
# perimeter wall
buildings["wall"] = mm.PerimeterWall(buildings, verbose=False).series
# generate binary contiguity-based W objects of first order and inclusive order 3
W1 = libpysal.weights.Queen.from_dataframe(tessellation, ids="uID")
W3 = mm.sw_high(k=3, weights=W1)
# building adjacency
buildings["adjacency"] = mm.BuildingAdjacency(buildings, W3, "uID", verbose=False).series
# interbuilding distance
buildings['neighbour_distance'] = mm.NeighborDistance(buildings, W1, 'uID', verbose=False).series
# generate networkx.MultiGraph object
G = mm.gdf_to_nx(streets)
# meshedness
G = mm.meshedness(G, verbose=False)
# convert networkx.MultiGraph to geopandas.GeoDataFrames
nodes, edges = mm.nx_to_gdf(G)
"""
link all characters to tessellation cells
"""
# merge street network edges based on proximity
edges["nID"] = range(len(edges))
buildings["nID"] = mm.get_network_id(buildings, edges, "nID", 500, verbose=False)
buildings = buildings.merge(edges.drop(columns="geometry"), on="nID", how="left")
# merge nodes based on edge ID and proximity
buildings["nodeID"] = mm.get_node_id(buildings, nodes, edges, "nodeID", "nID", verbose=False)
buildings = buildings.merge(nodes.drop(columns="geometry"), on="nodeID", how="left")
# merge buildings based on a shared attribute
tessellation = tessellation.merge(buildings.drop(columns="geometry"), on="uID", how="left")
"""
save all to GeoPackage
"""
tessellation.to_file(f"~/work/{cases.loc[i, 'case']}.gpkg", layer="tessellation", driver="GPKG")
buildings.to_file(f"~/work/{cases.loc[i, 'case']}.gpkg", layer="buildings", driver="GPKG")
edges.to_file(f"~/work/{cases.loc[i, 'case']}.gpkg", layer="edges", driver="GPKG")
nodes.to_file(f"~/work/{cases.loc[i, 'case']}.gpkg", layer="nodes", driver="GPKG")
print(f"{cases.loc[i, 'case']} done in {time() - s} seconds.")