Note

This page was generated from user_guide/migration.ipynb.
Interactive online version: Binder badge

Migration to momepy 1.0 API#

Starting with version 0.8, momepy contains a completely new API (and refactored internals) that will become the only API in the momepy 1.0. Given this is a complete reimplementation of nearly the entire package, it is not compatible with the legacy class-based API. This notebook contains a succinct migration guide, highlighting the key differences between the two APIs and outlines required changes to existing code to make it compatible with the upcoming 1.0 release.

[1]:
import geopandas as gpd
from libpysal import graph, weights

import momepy

buildings = gpd.read_file(
    momepy.datasets.get_path("bubenec"), layer="buildings"
)
tessellation = gpd.read_file(
    momepy.datasets.get_path("bubenec"), layer="tessellation"
)

Functions over classes#

The first key difference you may notice is the vast majority of functionality is now offered as functions, rather than classes. Take equivalent rectangular index as an example and its assignment as a new column.

The new API is simple:

[2]:
buildings["eri"] = momepy.equivalent_rectangular_index(buildings)
/Users/martin/miniforge3/envs/momepy/lib/python3.11/site-packages/pandas/core/arraylike.py:492: RuntimeWarning: invalid value encountered in oriented_envelope
  return getattr(ufunc, method)(*new_inputs, **kwargs)

The old API, which further required extracting the series from the class:

[3]:
buildings["eri"] = momepy.EquivalentRectangularIndex(buildings).series
/var/folders/2f/fhks6w_d0k556plcv3rfmshw0000gn/T/ipykernel_47459/3862675108.py:1: FutureWarning: Class based API like `momepy.EquivalentRectangularIndex` is deprecated. Replace it with `momepy.equivalent_rectangular_index` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0.
  buildings["eri"] = momepy.EquivalentRectangularIndex(buildings).series
/Users/martin/miniforge3/envs/momepy/lib/python3.11/site-packages/pandas/core/arraylike.py:492: RuntimeWarning: invalid value encountered in oriented_envelope
  return getattr(ufunc, method)(*new_inputs, **kwargs)

When running the code, you will see a warning about the deprecation.

FutureWarning: Class based API like `momepy.EquivalentRectangularIndex` is deprecated. Replace it with `momepy.equivalent_rectangular_index` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0.

If there is a direct equivalent, it also tells you its name. In some cases, there is no equivalent in momepy but one elsewhere.

Measuring area with the new API will require using geopandas directly.

[4]:
buildings["area"] = buildings.area

While the legacy API offered a wrapper.

[5]:
buildings["area"] = momepy.Area(buildings).series
/var/folders/2f/fhks6w_d0k556plcv3rfmshw0000gn/T/ipykernel_47459/1190336779.py:1: FutureWarning: `momepy.Area` is deprecated. Replace it with `.area` attribute of a GeoDataFrame or pin momepy version <1.0. This class will be removed in 1.0.
  buildings['area'] = momepy.Area(buildings).series

The warning is a bit different but still provides guidance.

FutureWarning: `momepy.Area` is deprecated. Replace it with `.area` attribute of a GeoDataFrame or pin momepy version <1.0. This class will be removed in 1.0.

Dependency on libpysal Graph over W#

Spatial relationships in the legacy API are represented using libpysal.weights.W objects. In the new one, momepy depends on the new libpysal.graph.Graph implementation. That has two consequences - different ways of building the object and reliance on GeoDataFrame indices.

Graph encodes geometries using the index they have in the GeoDataFrame. It does not use positional indexing nor custom column. The two objects are tied together via the index. For momepy, this means that indices plays a central role in the implementation and there’s no "unique_id" column any longer, which is superseded by index.

Example of computing a number of neighbors relative to the perimeter of each geometry using the new API:

[6]:
# build contiguity of order 2
contiguity_k2 = graph.Graph.build_contiguity(tessellation).higher_order(
    2, lower_order=True
)

# measure neighbors
tessellation["neighbours_weighted"] = momepy.neighbors(
    tessellation, contiguity_k2, weighted=True
)

And using the old API:

[7]:
contiguity_k2 = momepy.sw_high(k=2, gdf=tessellation, ids="uID")

tessellation["neighbours"] = momepy.Neighbors(
    tessellation, contiguity_k2, "uID", weighted=True
).series
/var/folders/2f/fhks6w_d0k556plcv3rfmshw0000gn/T/ipykernel_47459/1307576710.py:1: FutureWarning: `momepy.sw_high` is deprecated. Replace it with .higher_order() method of libpysal.graph.Graph or pin momepy version <1.0. This class will be removed in 1.0.
  contiguity_k2 = momepy.sw_high(k=2, gdf=tessellation, ids='uID')
/var/folders/2f/fhks6w_d0k556plcv3rfmshw0000gn/T/ipykernel_47459/1307576710.py:3: FutureWarning: Class based API like `momepy.Neighbors` is deprecated. Replace it with `momepy.neighbors` to use functional API instead or pin momepy version <1.0. Class-based API will be removed in 1.0.
  tessellation['neighbours'] = momepy.Neighbors(tessellation, contiguity_k2,'uID', weighted=True).series

Note that the sw_high function allowed bulding contiguity only.

Reliance on index#

When we need to capture the relationship between two objects (e.g., a GeoDataFrame and its Graph), the primary method is to rely on index. Unlike momepy 0.7, which heavily depends on columns with IDs mapping rows of one GeoDataFrame to the other, the new API attempts to minimise use of such columns. Below is the overview of the logic used in various situations.

Geometry and Graph#

This case is easy. Graph is mapped to geometry (either a GeoSeries or a GeoDataFrame) via index of the GeoPandas object.

contiguity = graph.Graph.build_contiguity(geometry)
momepy.neighbor_distance(geometry, contiguity)

In this case, we ensure that the index of geometry does not change and, in some cases, that the order of rows is also preserved to ensure the mapping of values to sparse arrays is not mismatched.

Series and Graph#

A subset of the case above is linking a pandas.Series to the Graph. Such a situation assumes that the index of the Series is equal to the index of the original geometry from which the Graph was created.

# typically, the Series is taken directly from the DataFrame
contiguity = graph.Graph.build_contiguity(geometry)
momepy.alignment(geometry["orientation"], contiguity)

Geometry and two Graphs#

Another subset is when you need to link geometry to two Graphs. In that case, both Graphs need to be based on the same index.

adjacency_graph = graph.Graph.build_contiguity(geometry)
neighborhood_graph = graph.Graph.build_distance_band(
    geometry,
    threshold=400,
)
momepy.mean_interbuilding_distance(
    geometry,
    adjacency_graph,
    neighborhood_graph,
)

Geometry and Geometry#

When linking two geometry arrays together – for example capturing which building belongs to which street segment, or which building belongs to which block/enclosure – you cannot rely solely on indices as the two objects do not match. In this situation, momepy will use the index of one Series, typically the shorter one, and a Series (i.e. a column) in another.

buildings["street_index"] = momepy.get_nearest_street(
    buildings,
    street_edges,
)
momepy.street_alignment(
    buildings["orientation"],
    street_edges["orientation"],
    network_id="street_index",
)