Skip to content

Add a generic function to set graph's nodes raster attributes #1289

@Bencpr

Description

@Bencpr

Contributing guidelines

  • I understand the contributing guidelines

Documentation

  • My proposal is not addressed by the documentation or examples

Existing issues

  • Nothing similar appears in an existing issue

What problem does your feature proposal solve?

Hi @gboeing,

I've been using the existing function ox.elevation.add_node_elevations_raster for different raster sources (elevation, land cover, etc ... ) by tweaking it a little bit to fit my needs.

My problem is that the function set the graph's nodes attributes to elevation, why I think could be relaxed to let the user choose an attribute name.

My current workaround is :

  • call ox.elevation.add_node_elevations_raster for sources other than elevation
  • rename node's attributes 'elevation' to something else
  • ....
  • finally call l ox.elevation.add_node_elevations_raster for elevation data, otherwise it would be overriden by precedent calls.

I would like to propose a more generic approach that could fit any kind of raster source.

Thank you for this awesome library !

Best,
Ben

What is your proposed solution?

Here is the proposed solution, by simply providing an attribute_name to the function:

def add_node_attribute_raster(
    G: nx.MultiDiGraph,
    filepath: str | Path | Iterable[str | Path],
    attribute_name: str,
    *,
    band: int = 1,
    cpus: int | None = None,
) -> nx.MultiDiGraph:
    """
    Add `attribute_name` attributes to all nodes from local raster file(s).

    If `filepath` is an iterable of paths, this will generate a virtual raster
    composed of the files at those paths as an intermediate step.

    See also the `add_edge_grades` function.

    Parameters
    ----------
    G
        Graph in same CRS as raster.
    filepath
        The path(s) to the raster file(s) to query.
    attribute_name
        The name of the node's attributes to be set.
    band
        Which raster band to query.
    cpus
        How many CPU cores to use. If None, use all available.

    Returns
    -------
    G
        Graph with `elevation` attributes on the nodes.
    """
    if rasterio is None:  # pragma: no cover
        msg = "rasterio must be installed as an optional dependency to query rasters."
        raise ImportError(msg)

    # if multiple filepaths are passed in, compose them as a virtual raster
    if not isinstance(filepath, (str, Path)):
        filepath = _build_vrt_file(filepath)

    if cpus is None:
        cpus = mp.cpu_count()
    cpus = min(cpus, mp.cpu_count())
    msg = f"Attaching elevations with {cpus} CPUs..."
    utils.log(msg, level=lg.INFO)

    nodes = convert.graph_to_gdfs(G, edges=False, node_geometry=False)[["x", "y"]]
    if cpus == 1:
        elevs = dict(_query_raster(nodes, filepath, band))
    else:
        # divide nodes into equal-sized chunks for multiprocessing
        size = int(np.ceil(len(nodes) / cpus))
        args = ((nodes.iloc[i : i + size], filepath, band) for i in range(0, len(nodes), size))
        with mp.get_context().Pool(cpus) as pool:
            results = pool.starmap_async(_query_raster, args).get()
        elevs = {k: v for kv in results for k, v in kv}

    nx.set_node_attributes(G, elevs, name=attribute_name)
    msg = f"Added {attribute_name} data from raster to all nodes"
    utils.log(msg, level=lg.INFO)
    return G

What alternatives have you considered?

None.

Additional context

Current workaround:

def add_nodes_raster_attributes(G, filepath: str | Path, attribute_name: str, dtype=float):
    """
    Add raster attributes to nodes.
    """
    # This will create an 'elevation' attributes to all nodes
    G = ox.elevation.add_node_elevations_raster(G, filepath, cpus=1)

    # create dict with desired dtypes and set attributes with attr name
    data_dtyped = {
        node: dtype(value) if not np.isnan(value) else np.nan 
        for node, value in nx.get_node_attributes(G, "elevation").items()
    }
    nx.set_node_attributes(G, data_dtyped, name=attribute_name)

    # remove elevation attribute
    nx.remove_node_attributes(G, "elevation")

    return G

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions