Workflow System¶
Overview¶
The flow cartogram module provides two API levels:
- Low-level:
morph_geometries(geometries, values, options)operates on raw Shapely geometry sequences and returns aCartogramresult object. It has no dependency on geopandas. - High-level:
CartogramWorkflowwrapsmorph_geometries()with GeoDataFrame input/output, automatic area-unit handling, and multi-run state management. This is the primary entry point for most use cases.
The two are related as follows: each call to CartogramWorkflow.morph() invokes morph_geometries() internally and wraps the result in a Cartogram stored on the workflow.
Relevant source files: workflow.py, cartogram.py, serialization.py.
CartogramWorkflow¶
Purpose¶
CartogramWorkflow manages a sequence of morphing runs on a fixed input GeoDataFrame. The original (unmorphed) state is always preserved at index 0; each subsequent morph appends a new Cartogram to the result list. The target density is always computed from the original data, so it remains invariant across runs and refinements.
Construction¶
CartogramWorkflow(
gdf, # GeoDataFrame with polygon geometries
column, # name of the value column
landmarks=None, # optional GeoDataFrame of landmark geometries
coords=None, # optional coordinate array/grid/mesh to displace
options=None, # MorphOptions (default options used if None)
density_per=None, # unit string: 'm2', 'km2', 'ha', 'acre', 'sqft', 'sqmi'
)
density_per is a convenience parameter. If supplied, the workflow inspects the GeoDataFrame's CRS to determine the native area unit and computes the area_scale for MorphOptions automatically. For example, density_per='km2' when the CRS is in metres sets area_scale=1e-6 so that densities are expressed per km². Alternatively, area_scale can be set directly on MorphOptions.
On construction the workflow creates the index-0 Cartogram representing the unmodified input (status = MorphStatus.ORIGINAL).
Internal State¶
| Attribute | Description |
|---|---|
_original_gdf |
Copy of the input GeoDataFrame. Never modified. |
_column |
Value column name. |
_results |
list[Cartogram]. Index 0 = original; index 1+ = morph results. |
_original_landmarks_gdf |
Copy of landmark GeoDataFrame, or None. |
Morphing¶
morph(options=None, **overrides) → Cartogram
Runs one complete morphing pass. The input geometry for the pass is taken from the latest Cartogram in _results; the target density is recomputed from the original data. Options can be fully replaced or individual fields overridden by passing keyword arguments.
morph_multiresolution(min_resolution, levels, margin, square, options=None) → Cartogram
Builds a dyadic grid hierarchy with levels grids and runs one morphing pass per grid level, from coarsest to finest. Each pass's output geometry is the input to the next. Each pass appends a separate Cartogram to _results. Execution stops early if the coarser-level result has already converged.
prescale_components is applied only at the first (coarsest) level; subsequent levels disable it automatically to avoid rescaling already-morphed geometry.
Access and State Management¶
workflow[0] # original Cartogram
workflow[-1] # latest Cartogram (same as workflow.latest)
workflow.original # same as workflow[0]
workflow.latest # same as workflow[-1]
workflow.is_morphed # True if any morph runs exist
workflow.pop(n=1) # remove and return last n Cartograms (cannot remove original)
workflow.reset() # discard all morph results, keeping only original
Export¶
workflow.to_geodataframe(
run_id=None, # index into _results; defaults to latest
iteration=None, # iteration within that run; defaults to latest
include_errors=True,
include_density=True,
)
Delegates to Cartogram.to_geodataframe(). Returns a GeoDataFrame with the morphed geometry and optional error/density columns.
Cartogram¶
Cartogram is a dataclass that holds the complete result of a single morph_geometries() call. It is created by the algorithm and stored on CartogramWorkflow._results, but it can also be used independently via the low-level API.
Data Held¶
| Field | Type | Description |
|---|---|---|
snapshots |
History[CartogramSnapshot] |
Snapshots of the morphed state at saved iterations |
convergence |
ConvergenceHistory |
Scalar error metrics recorded at every iteration |
status |
MorphStatus |
Termination reason |
niterations |
int |
Number of iterations completed |
duration |
float |
Wall-clock time in seconds |
options |
MorphOptions or None |
Options used for this run |
grid |
Grid or None |
Grid used for this run |
target_density |
float or None |
Equilibrium density \(\rho^*\) |
internals |
History[CartogramInternalsSnapshot] or None |
Grid-level debug snapshots, if save_internals=True |
Source references (_source_gdf, _value_column) are stored as private attributes and enable to_geodataframe() and plot() to reconstruct a GeoDataFrame without requiring the caller to supply the original data again.
MorphStatus¶
| Value | Meaning |
|---|---|
ORIGINAL |
Unmorphed placeholder; no algorithm was run |
CONVERGED |
Both mean_tol and max_tol thresholds were met |
STALLED |
Max error increased for stall_patience consecutive iterations |
COMPLETED |
n_iter iterations reached without converging |
FAILED |
An exception occurred during morphing |
Accessing Results¶
All accessor methods accept an optional iteration parameter. When omitted, the value defaults to the latest available snapshot.
cartogram.latest # most recent CartogramSnapshot
cartogram.get_errors(iteration=None) # MorphErrors with per-geometry arrays
cartogram.get_geometry(iteration=None)
cartogram.get_density(iteration=None)
cartogram.get_landmarks(iteration=None)
cartogram.get_coords(iteration=None)
For convergence history (scalar metrics, available for every iteration):
cartogram.get_convergence_errors(iteration=None) # returns ErrorRecord
cartogram.convergence.to_dict() # dict of arrays for plotting
Export¶
Returns a GeoDataFrame with the morphed geometry replacing the original, plus optional columns mean_error_pct, max_error_pct, and per-geometry error_pct and density.
Delegates to visualization.plot_cartogram() and returns a CartogramPlotResult containing the matplotlib axes and named artist references.
Serialization¶
JSON (cartogram.save(path) / Cartogram.load(path))
Saves the latest snapshot's geometries as GeoJSON plus the convergence history, options (numeric fields only), grid parameters, and status. A loaded Cartogram contains only one snapshot but supports to_geodataframe() and plot() immediately. JSON format is suitable for archiving a final result or sharing with non-Python tools.
Pickle (save_workflow(workflow, path) / load_workflow(path))
Serializes the entire CartogramWorkflow including all runs and full Python objects. A loaded workflow can continue morphing from where it left off. Pickle format is intended for checkpointing long-running sessions.
Convergence history export (export_history(cartogram, path))
Writes the ConvergenceHistory arrays to CSV or JSON. Useful for producing convergence plots outside Python.