@@ -276,6 +276,123 @@ def to_nonperiodic(self, xmax: float):
276276 grid , obj = self .grid .to_nonperiodic (xmax = xmax , obj = self .obj )
277277 return UgridDataArray (obj , grid )
278278
279+ def _to_facet (self , facet : str , newdim : str ):
280+ """
281+ Map the data from one facet to another.
282+
283+ Parameters
284+ ----------
285+ facet: str
286+ node, edge, face
287+ newdim: str
288+ how to name the new dimension, e.g. three nodes
289+ per triangle face, two nodes per edge, etc.
290+ """
291+ grid = self .grid
292+ obj = self .obj
293+
294+ gridfacets = grid .facets
295+ if facet not in gridfacets :
296+ raise ValueError (
297+ f"Cannot map to { facet } for a { type (grid ).__name__ } topology."
298+ )
299+
300+ if newdim in obj .dims :
301+ raise ValueError (
302+ f"Dimension { newdim } already exists. Please provide a new dimension name."
303+ )
304+
305+ source_dim = set (grid .dimensions ).intersection (obj .dims ).pop ()
306+ target_dim = getattr (grid , f"{ facet } _dimension" )
307+ if source_dim == target_dim :
308+ raise ValueError (
309+ f"No conversion needed, data is already { facet } -associated."
310+ )
311+
312+ # Find out on which facet we're currently located
313+ source = {v : k for k , v in gridfacets .items ()}[source_dim ]
314+ connectivity = grid .format_connectivity_as_dense (
315+ getattr (grid , f"{ facet } _{ source } _connectivity" )
316+ )
317+ indexer = xr .DataArray (connectivity , dims = (target_dim , newdim ))
318+ # Ensure the source dimension is not chunked for efficient indexing.
319+ obj = obj .chunk ({source_dim : - 1 })
320+ # Set the fill values (-1) to NaN
321+ mapped = obj .isel ({source_dim : indexer }).where (connectivity != - 1 )
322+ return UgridDataArray (mapped , grid )
323+
324+ def to_node (self , dim : str = "nmax" ):
325+ """
326+ Map data to nodes.
327+
328+ Creates a new dimension representing the contributing source elements
329+ for each node, as multiple faces/edges can connect to a single node.
330+
331+ Parameters
332+ ----------
333+ dim : str, optional
334+
335+ Returns
336+ -------
337+ mapped: UgridDataArray
338+ A new UgridDataArray with data mapped to the nodes of the grid.
339+
340+ Examples
341+ --------
342+ Compute the mean elevation based on the surrounding faces for each node:
343+
344+ >>> node_elevation = face_elevation.to_node().mean("contributors")
345+ """
346+ return self ._to_facet ("node" , dim )
347+
348+ def to_edge (self , dim : str = "nmax" ):
349+ """
350+ Map data to edges.
351+
352+ Creates a new dimension representing the contributing source elements
353+ for each node, as two nodes or two faces are connected to an edge.
354+
355+ Parameters
356+ ----------
357+ dim : str, optional
358+
359+ Returns
360+ -------
361+ mapped: UgridDataArray
362+ A new UgridDataArray with data mapped to the edges of the grid.
363+
364+ Examples
365+ --------
366+ Compute the mean elevation based on the nodes for each edge:
367+
368+ >>> edge_elevation = node_elevation.to_edge().mean("contributors")
369+ """
370+ return self ._to_facet ("edge" , dim )
371+
372+ def to_face (self , dim : str = "nmax" ):
373+ """
374+ Map data to faces.
375+
376+ Creates a new dimension representing the contributing source elements
377+ for each node, as two edges or multiple nodes are connected to a face.
378+
379+ Parameters
380+ ----------
381+ dim : str, optional
382+
383+ Returns
384+ -------
385+ mapped: UgridDataArray
386+ A new UgridDataArray with data mapped to the faces of the grid.
387+
388+ Examples
389+ --------
390+ Compute the mean elevation based on the nodes for each face:
391+
392+ >>> face_elevation = node_elevation.to_face().mean("contributors")
393+ """
394+ return self ._to_facet ("face" , dim )
395+
279396 def intersect_line (
280397 self , start : Sequence [float ], end : Sequence [float ]
281398 ) -> xr .DataArray :
0 commit comments