-
Notifications
You must be signed in to change notification settings - Fork 861
Closed
Description
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_rasterfor sources other than elevation - rename node's attributes 'elevation' to something else
- ....
- finally call l
ox.elevation.add_node_elevations_rasterfor 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
Labels
No labels