wolfhece.PyVertex._model ======================== .. py:module:: wolfhece.PyVertex._model .. autoapi-nested-parse:: Author: HECE - University of Liege, Pierre Archambeau Date: 2024 Copyright (c) 2024 University of Liege. All rights reserved. This script and its content are protected by copyright law. Unauthorized copying or distribution of this file, via any medium, is strictly prohibited. Module Contents --------------- .. py:data:: _NUMPY_MISSING .. py:class:: StorageMode Bases: :py:obj:`str`, :py:obj:`enum.Enum` .. autoapi-inheritance-diagram:: wolfhece.PyVertex._model.StorageMode :parts: 1 :private-bases: Storage backend mode for cloud vertices. .. py:attribute:: DICT :value: 'dict' .. py:attribute:: NUMPY :value: 'numpy' .. py:class:: wolfvertex(x: float, y: float, z: float = -99999.0) WOLF vertex — 3D point with associated values. Represents a point in space (x, y, z) with an optional dictionary of named values (e.g. elevation, discharge, concentration…). :ivar x: X coordinate (Easting) :ivar y: Y coordinate (Northing) :ivar z: Z coordinate (elevation), -99999. by default (= undefined) :ivar in_use: whether the vertex is active :ivar values: dictionary ``{key: value}`` of associated quantities, ``None`` if empty .. py:attribute:: x :type: float .. py:attribute:: y :type: float .. py:attribute:: z :type: float .. py:attribute:: values :type: dict .. py:attribute:: in_use :value: True .. py:method:: rotate(angle: float, center: tuple) Rotate the vertex :param angle: angle in radians (positive for counterclockwise) :param center: center of the rotation (x, y) .. py:method:: as_shapelypoint() -> shapely.geometry.Point Convert the vertex to a ``shapely.geometry.Point``. :return: Shapely Point object (x, y, z) .. py:method:: copy() -> wolfvertex Independent copy of the vertex (``values`` are not copied). :return: new ``wolfvertex`` with the same coordinates .. py:method:: getcoords() -> numpy.ndarray Return coordinates as a NumPy array ``[x, y, z]``. :return: ``np.ndarray`` of shape (3,) .. py:method:: dist3D(v: wolfvertex) -> float Return the 3D distance to another vertex :param v: vertex to compare .. py:method:: dist2D(v: wolfvertex) -> float Return the 2D distance to another vertex :param v: vertex to compare .. py:method:: addvalue(id, value) Add an associated value to the vertex. Creates the ``values`` dictionary if it does not exist yet. :param id: key identifying the value (e.g. ``'discharge'``, ``'concentration'``) :param value: value to associate (numeric, string…) .. py:method:: add_value(id, value) Alias for :meth:`addvalue` — add an associated value to the vertex. :param id: key identifying the value :param value: value to associate .. py:method:: add_values(values: dict) Add multiple associated values to the vertex at once. :param values: dictionary ``{key: value}`` to merge into the vertex .. py:method:: getvalue(id) Return the value associated with the key *id*. :param id: key of the requested value :return: the value if it exists, ``None`` otherwise .. py:method:: get_value(id) Alias for :meth:`getvalue` — return an associated value from the vertex. :param id: key of the requested value :return: the value if it exists, ``None`` otherwise .. py:method:: get_values(ids: list) -> dict Return a subset of values associated with the vertex. :param ids: list of keys to extract :return: dictionary ``{key: value}`` containing only the found keys .. py:method:: limit2bounds(bounds=None) Clamp the vertex coordinates to the given bounding box. Modifies ``self.x`` and ``self.y`` *in place*. :param bounds: bounding box ``[[xmin, xmax], [ymin, ymax]]``. If ``None``, no action is taken. .. py:method:: is_like(v: wolfvertex, tol: float = 1e-06) -> bool Test near-equality with another vertex. Comparison is done component-wise (x, y, z) using absolute differences. :param v: reference vertex to compare against :param tol: absolute tolerance on each component (default 1e-6) :return: ``True`` if all three differences are below *tol* .. py:class:: cloudproperties(lines=[], parent: cloud_vertices = None) Visual and legend properties for a cloud of vertices. Stores the display configuration (color, size, style, transparency…) as well as the legend parameters associated with the cloud. :ivar used: whether these properties are active :ivar color: drawing color (RGB integer encoded by ``getIfromRGB``) :ivar width: point size in pixels :ivar style: rendering style index (see ``Cloud_Styles`` in the GUI) :ivar alpha: opacity (0 = opaque, 255 = fully transparent) :ivar filled: symbol fill (True = filled) :ivar legendvisible: legend display (True = visible) :ivar transparent: OpenGL transparency toggle :ivar animationspeed: animation speed multiplier (cycles per second) :ivar animationmode: animation mode (0=none, 1=blink, 2=fade, 3=grow, 4=seasons, 5=pulse) :ivar animationamplitude: animation amplitude factor :ivar legendtext: text displayed in the legend :ivar legendrelpos: relative legend position (1–9, numpad layout) :ivar legendx: absolute X coordinate of the legend (when ``legendrelpos == 0``) :ivar legendy: absolute Y coordinate of the legend (when ``legendrelpos == 0``) :ivar legendbold: bold text :ivar legenditalic: italic text :ivar legendunderlined: underlined text :ivar legendfontname: font name (e.g. ``'Arial'``) :ivar legendfontsize: font size in points :ivar legendcolor: legend text color (RGB integer) :ivar legendpriority: rendering priority used by ``Font_Priority`` :ivar legendorientation: text orientation angle in degrees :ivar legendwidth: legend texture width in pixels :ivar legendheight: legend texture height in pixels :ivar renderingmode: OpenGL backend for cloud points (0=list, 1=shader) :ivar symbolpreset: symbol selected from the bundled ``wolfhece/symbols`` library :ivar symbolsource: shared symbol image path used when style=SYMBOL :ivar symboltintwithcolor: if ``True``, multiplies symbol by draw color :ivar symbolrastersize: target SVG rasterization size in pixels :ivar symbolrotation: per-cloud symbol rotation in degrees (CCW, Option A) :ivar symbolscale: per-cloud symbol scale factor (Option A) :ivar highlightselectedpoint: if ``True``, highlights currently selected cloud point in interactive tools :ivar highlightselectedpointsizefactor: multiplicative size factor for the selected point highlight marker :ivar highlightselectedpointcolor: color used for selected point highlight marker (RGB integer) :ivar extrude: if ``True``, the cloud is extruded in 3D (rendering only) .. py:attribute:: used :type: bool .. py:attribute:: color :type: int .. py:attribute:: width :type: int .. py:attribute:: style :type: int .. py:attribute:: alpha :type: int .. py:attribute:: filled :type: bool .. py:attribute:: legendvisible :type: bool .. py:attribute:: transparent :type: bool .. py:attribute:: animationspeed :type: float .. py:attribute:: animationmode :type: int .. py:attribute:: animationamplitude :type: float .. py:attribute:: legendtext :type: str .. py:attribute:: legendrelpos :type: int .. py:attribute:: legendx :type: float .. py:attribute:: legendy :type: float .. py:attribute:: legendbold :type: bool .. py:attribute:: legenditalic :type: bool .. py:attribute:: legendunderlined :type: bool .. py:attribute:: legendfontname .. py:attribute:: legendfontsize :type: int .. py:attribute:: legendcolor :type: int .. py:attribute:: legendpriority :type: int .. py:attribute:: legendorientation :type: int .. py:attribute:: legendwidth :type: int .. py:attribute:: legendheight :type: int .. py:attribute:: renderingmode :type: int .. py:attribute:: symbolpreset :type: str .. py:attribute:: symbolsource :type: str .. py:attribute:: symboltintwithcolor :type: bool .. py:attribute:: symbolrastersize :type: int .. py:attribute:: symbolrotation :type: float .. py:attribute:: symbolscale :type: float .. py:attribute:: highlightselectedpoint :type: bool .. py:attribute:: highlightselectedpointsizefactor :type: float .. py:attribute:: highlightselectedpointcolor :type: int .. py:attribute:: extrude :type: bool :value: False .. py:attribute:: parent :value: None .. py:method:: to_dict() -> dict Serialize properties to a plain dictionary. Colors are stored as ``[R, G, B]`` lists for readability. .. py:method:: from_dict(d: dict, parent: cloud_vertices = None) -> cloudproperties :classmethod: Create a :class:`cloudproperties` from a dictionary. :param d: dictionary as produced by :meth:`to_dict`. :param parent: owning cloud instance. :return: new :class:`cloudproperties` instance. .. py:class:: cloud_vertices(fname: Union[str, pathlib.Path] = '', fromxls: str = '', header: bool = False, toload=True, idx: str = '', bbox: shapely.geometry.Polygon = None, dxf_imported_elts=['MTEXT', 'INSERT'], **kwargs) 3D point cloud with associated values. Supported formats: DXF (``.dxf``), Shapefile (``.shp``), ASCII (all others). For ASCII files, the separator is auto-detected among tab, semicolon, comma and space. DXF format is recognised by the file extension; otherwise an ASCII file is assumed. If a header exists on the first line, it must be indicated with ``header=True``. The total number of columns (*nb*) determines the interpretation: - *nb* > 3: a header is required. - if ``header[2].lower() == 'z'``, the 3rd column is the Z elevation; otherwise all columns beyond the 1st are values associated with (X, Y). - number of values = *nb* − (2 or 3) depending on whether Z is present. Data are stored in ``myvertices`` (indexed dictionary): .. code-block:: python {0: {'vertex': wolfvertex, 'head1': val1, 'head2': val2, ...}, 1: {'vertex': wolfvertex, ...}, ...} See :meth:`readfile`, :meth:`import_from_dxf`, :meth:`import_from_shapefile`. :ivar filename: source file path (empty string if created in memory) :ivar myvertices: dictionary ``{id: {'vertex': wolfvertex, key: value, ...}}`` :ivar xbounds: tuple ``(xmin, xmax)`` of the X extent :ivar ybounds: tuple ``(ymin, ymax)`` of the Y extent :ivar zbounds: tuple ``(zmin, zmax)`` of the Z extent :ivar myprop: visual properties (:class:`cloudproperties` instance) :ivar mytree: Scipy KDTree, ``None`` until :meth:`create_kdtree` is called :ivar loaded: ``True`` if data was loaded successfully :ivar idx: text identifier of the cloud .. py:attribute:: filename :type: str .. py:property:: myvertices :type: dict Legacy row storage accessor. Reading ``myvertices`` guarantees a dict-based view. If the cloud is currently in NumPy backend mode, rows are materialized first. .. py:attribute:: _myvertices :type: dict[int, dict['vertex':wolfvertex, str:float]] .. py:attribute:: xbounds :type: tuple .. py:attribute:: ybounds :type: tuple .. py:attribute:: zbounds :type: tuple .. py:attribute:: myprop :type: cloudproperties .. py:attribute:: mytree :type: scipy.spatial.KDTree .. py:attribute:: _mytree_dim :type: int | None .. py:attribute:: AUTO_NUMPY_SWITCH_THRESHOLD :value: 100000 .. py:attribute:: idx :value: '' .. py:attribute:: parent_collection :type: cloud_of_clouds | None :value: None .. py:attribute:: xmin :value: 0.0 .. py:attribute:: ymin :value: 0.0 .. py:attribute:: xmax :value: 0.0 .. py:attribute:: ymax :value: 0.0 .. py:attribute:: _numpy_xyz :value: None .. py:attribute:: _numpy_keys :value: None .. py:attribute:: _numpy_values .. py:attribute:: loaded :value: False .. py:attribute:: _header :value: False .. py:method:: on_changed_vertices() Hook called after vertices are added/removed/updated. Base model implementation is a no-op. GUI subclasses can override this method to invalidate OpenGL caches and trigger a redraw. .. py:method:: _make_cloud_vertices(**kwargs) -> cloud_vertices Create a sibling cloud_vertices. GUI subclass returns the GUI variant. .. py:method:: _make_cloudproperties(**kwargs) -> cloudproperties Create a cloudproperties. GUI subclass returns the GUI variant. .. py:method:: _make_cloudproperties_from_dict(d: dict, **kwargs) -> cloudproperties Create a cloudproperties from a dictionary. GUI subclass returns the GUI variant. .. py:property:: myname :type: str Cloud name accessor (alias for ``idx``). .. py:method:: _materialize_numpy_storage() Convert optional NumPy storage back to legacy dict rows. .. py:method:: _reset_storage_for_reload() Clear both storage backends before a full data reload. .. py:property:: storage_mode :type: StorageMode Current storage backend for cloud rows. .. py:method:: switch_storage_mode(mode: Union[Literal['dict', 'numpy'], StorageMode] = StorageMode.DICT) Switch storage backend between legacy dict rows and NumPy arrays. :param mode: target backend. ``'dict'`` materializes rows in ``myvertices``; ``'numpy'`` compacts current rows into array storage while preserving row keys. .. py:method:: create_kdtree() Build a Scipy KDTree from the current vertex coordinates. The KDTree is stored in ``self.mytree`` and used by :meth:`find_nearest` for nearest-neighbor queries. .. py:method:: _is_undefined_z(z: numpy.ndarray | float, atol: float = 1e-09) :staticmethod: Return mask/flag for coordinates considered undefined in Z. .. py:property:: z_dimension_mode :type: Literal['2d', '3d', 'mixed'] Describe cloud Z content mode. - ``'2d'``: all Z are undefined (default sentinel ``-99999``) - ``'3d'``: all Z are defined - ``'mixed'``: both defined and undefined Z values coexist .. py:method:: _normalize_query_xyz(xyz: numpy.ndarray | list) -> numpy.ndarray Normalize query coordinates to a 2D float64 array. .. py:method:: _select_kdtree_dim(query_cols: int) -> int | None Choose KDTree dimensionality (2 or 3) based on cloud/query context. .. py:method:: _get_query_and_tree(xyz: numpy.ndarray | list) Return normalized query array, KDTree and row keys for nearest search. .. py:method:: find_nearest(xyz: numpy.ndarray | list, nb: int = 1) Find nearest neighbors from Scipy KDTree structure based on a copy of the vertices. See : https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.KDTree.query.html :param xyz: coordinates to find nearest neighbors -- shape (n, m) - where m is the number of coordinates (2 or 3) :param nb: number of nearest neighbors to find :return: list of distances, list of "Wolfvertex", list of elements stored in self.myvertices - or list of lists if xyz is a list of coordinates .. py:method:: find_nearest_id(xyz: numpy.ndarray | list, max_distance: float | None = None) Find nearest row id(s) for one or several query points. :param xyz: coordinates used for nearest-neighbor search. Accepted shapes: ``[x, y, z]`` or ``[[x, y, z], ...]``. :param max_distance: optional upper bound on accepted nearest distance. If provided and the nearest point is farther than this value, ``None`` is returned for that query. :return: nearest id (single query), list of nearest ids (multiple queries), or ``None`` on error. .. py:method:: init_from_nparray(array: numpy.ndarray, numpy_backend: bool | None = None) Populate the cloud from a NumPy array. Existing vertices are overwritten (added with sequential keys starting from 0). :param array: array of shape ``(n, 3)`` with columns X, Y, Z. :param numpy_backend: backend selection mode: - ``True``: force NumPy backend; - ``False``: force legacy dict rows; - ``None`` (default): auto-switch to NumPy when ``len(array) >= AUTO_NUMPY_SWITCH_THRESHOLD`` (default threshold: 100_000 points). .. py:method:: readfile(fname: str = '', header: bool = False) Reading an ascii file with or without header :param fname: (str) file name :param header: (bool) header in file (first line with column names) The separator is automatically detected among : tabulation, semicolon, space, comma. The file must contain at least 2 columns (X, Y) and may contain a third one (Z) and more (values). If values are present, they are stored in the dictionnary with their header name as key. .. py:method:: import_from_dxf(fn: str = '', imported_elts=['MTEXT', 'INSERT']) Import points from a DXF file using the ``ezdxf`` library. Supported entity types: MTEXT, INSERT, POLYLINE, LWPOLYLINE, LINE. Only entities on visible (active) layers are imported. For MTEXT/INSERT, points with Z == 0 are skipped. :param fn: DXF file path. If empty or non-existent, no action is taken. :param imported_elts: list of DXF entity types to import (e.g. ``['MTEXT', 'INSERT', 'POLYLINE', 'LINE']``). :return: number of imported points, or ``None`` if the file is not found. .. py:method:: _resolve_shapefile_column(gdf, targetcolumn: str) -> str Resolve the column to use when *targetcolumn* is not found. Called by :meth:`import_from_shapefile` when neither *targetcolumn* nor ``'geometry'`` are available. The base implementation logs an error and returns ``None``. The GUI subclass overrides this to present an interactive column chooser. :param gdf: ``GeoDataFrame`` already loaded from the Shapefile. :param targetcolumn: the originally requested column name. :return: resolved column name, or ``None`` to abort the import. .. py:method:: _resolve_value_columns(gdf, value_columns, excluded_columns: list[str] | None = None) -> list[str] Resolve the list of attributes to import from a GeoDataFrame. :param gdf: source ``GeoDataFrame``. :param value_columns: ``None`` (disabled), ``'all'`` or explicit iterable. :param excluded_columns: columns that must not be imported. :return: list of selected column names. .. py:method:: _import_from_geodataframe(gdf, source_label: str, targetcolumn: str = 'X1_Y1_Z1', value_columns=None) Import points and optional attributes from an existing GeoDataFrame. Three extraction strategies are tried in order: 1. *targetcolumn* is present: each cell is a ``'X,Y,Z'`` comma-separated string (format used by SPW-ARNE-DCENN). 2. A ``geometry`` column is present: coordinates are read from the Shapely ``Point`` geometry of each row. 3. Neither: :meth:`_resolve_shapefile_column` is called to let subclasses (e.g. the GUI) choose an alternative column. After import, :meth:`find_minmax` is called and ``self.loaded`` is set to ``True``. :param gdf: source ``GeoDataFrame`` (already read by the caller). :param source_label: human-readable file path used in log/error messages. :param targetcolumn: name of the column containing ``'X,Y,Z'`` coordinate strings. Default: ``'X1_Y1_Z1'``. :param value_columns: optional attribute import selector. ``None`` (default) imports geometry only; ``'all'`` imports all non-geometry/non-coordinate columns; a list/tuple/set imports the named columns only. If the number of imported rows reaches ``AUTO_NUMPY_SWITCH_THRESHOLD``, storage is automatically switched to the NumPy backend. :return: number of imported points, or ``None`` on error. .. py:method:: import_from_shapefile(fn: str = '', targetcolumn: str = 'X1_Y1_Z1', bbox: shapely.geometry.Polygon = None, value_columns=None) Import points from a Shapefile using ``geopandas``. Two extraction modes: 1. If *targetcolumn* exists in the columns, each row is read as a ``'X,Y,Z'`` string (format used by SPW-ARNE-DCENN). 2. Otherwise, the ``geometry`` column is used (Point or MultiPoint). If neither is found, :meth:`_resolve_shapefile_column` is called to allow subclasses (e.g. the GUI) to propose an alternative column. :param fn: Shapefile path (``.shp``). If empty or non-existent, no action is taken. :param targetcolumn: column name containing coordinates as ``'X,Y,Z'``. :param bbox: Shapely polygon delimiting the area of interest. Passed to ``gpd.read_file(fn, bbox=...)`` to spatially filter features during reading. :param value_columns: optional attribute import selector. ``None`` (default) imports geometry only; ``'all'`` imports all non-geometry/non-XYZ-source columns; explicit list imports selected columns. :return: number of imported points, or ``None`` on error. .. py:method:: import_from_geopackage(fn: str = '', layer: str = None, targetcolumn: str = 'X1_Y1_Z1', bbox: shapely.geometry.Polygon = None, value_columns=None) Import points from a GeoPackage using ``geopandas``. :param fn: GeoPackage path (``.gpkg``). If empty or non-existent, no action is taken. :param layer: optional layer name. If ``None``, geopandas default layer is used. :param targetcolumn: column name containing coordinates as ``'X,Y,Z'``. :param bbox: optional spatial filter passed to ``gpd.read_file``. :param value_columns: optional attribute import selector. :return: number of imported points, or ``None`` on error. .. py:method:: _resolve_export_value_columns(value_columns) -> list[str] Resolve which attributes should be exported. :param value_columns: ``None`` (no attributes), ``'all'`` or explicit iterable. :return: ordered list of attribute names to export. .. py:method:: _build_geodataframe_for_export(value_columns='all', include_xyz_column: bool = True, xyz_column: str = 'X1_Y1_Z1', crs=None) Build a GeoDataFrame representation of the cloud for export. .. py:method:: export_to_shapefile(fn: str, value_columns='all', include_xyz_column: bool = True, xyz_column: str = 'X1_Y1_Z1', crs=None) Export cloud vertices to a Shapefile using ``geopandas``. :param fn: destination ``.shp`` path. :param value_columns: attributes to export (``None``, ``'all'`` or explicit iterable). :param include_xyz_column: write ``X,Y,Z`` CSV string column for roundtrip import. :param xyz_column: name of the optional XYZ text column. :param crs: optional CRS forwarded to GeoDataFrame. :return: number of exported points, or ``None`` on error. .. py:method:: export_to_geopackage(fn: str, layer: str = 'points', value_columns='all', include_xyz_column: bool = True, xyz_column: str = 'X1_Y1_Z1', crs=None) Export cloud vertices to a GeoPackage using ``geopandas``. :param fn: destination ``.gpkg`` path. :param layer: destination layer name. :param value_columns: attributes to export (``None``, ``'all'`` or explicit iterable). :param include_xyz_column: write ``X,Y,Z`` CSV string column for roundtrip import. :param xyz_column: name of the optional XYZ text column. :param crs: optional CRS forwarded to GeoDataFrame. :return: number of exported points, or ``None`` on error. .. py:method:: to_dict() -> dict Serialize the cloud to a plain dictionary. The dictionary contains the cloud identifier, visual properties, column headers, and vertices as a compact 2-D list. :return: dictionary suitable for :func:`json.dumps`. .. py:method:: from_dict(d: dict, **kwargs) -> cloud_vertices :classmethod: Create a :class:`cloud_vertices` from a dictionary. :param d: dictionary as produced by :meth:`to_dict`. :param kwargs: extra keyword arguments forwarded to the constructor (e.g. ``mapviewer``, ``plotted`` for the GUI subclass). :return: new :class:`cloud_vertices` instance. .. py:method:: save_json(fn: str | pathlib.Path, indent: int = 2) -> None Save the cloud to a JSON file. :param fn: destination file path. :param indent: JSON indentation level (``None`` for compact output). .. py:method:: load_json(fn: str | pathlib.Path, **kwargs) -> cloud_vertices :classmethod: Load a cloud from a JSON file. :param fn: source file path. :param kwargs: forwarded to :meth:`from_dict` (and then to the constructor, e.g. ``mapviewer``, ``plotted``). :return: new :class:`cloud_vertices` instance. :raises ValueError: if the file format is not recognized. .. py:method:: duplicate(idx: str | None = None, **kwargs) -> cloud_vertices Create a deep copy of this cloud. All vertices, properties and metadata are duplicated. The new cloud shares no mutable state with the original. :param idx: identifier for the copy. If ``None``, the original ``idx`` is reused. :param kwargs: extra keyword arguments forwarded to the constructor (e.g. ``mapviewer`` for the GUI). :return: independent :class:`cloud_vertices` copy. .. py:method:: copy(idx: str | None = None, **kwargs) -> cloud_vertices Alias for duplicate method. .. py:method:: iter_on_vertices() Generator over the cloud vertices. :yields: :class:`wolfvertex` instances one by one. .. py:method:: iter_rows() Yield cloud rows as ``(row_id, row_dict)`` for both backends. ``row_dict`` always contains at least ``{'vertex': wolfvertex(...)}``. In NumPy backend mode, additional value columns are attached when present for that row. .. py:property:: nbvertices :type: int Number of vertices in the cloud .. py:property:: xyz :type: numpy.ndarray Alias for get_xyz method .. py:method:: get_xyz(key='vertex') -> numpy.ndarray Return the vertices as numpy array :param key: key to be used for the third column (Z) -- 'vertex' or any key in the dictionnary -- if 'vertex'', [[X,Y,Z]] are returned .. py:property:: has_values :type: bool Check if the cloud has values (other than X,Y,Z) .. py:property:: has_value_columns :type: bool Whether rows include value columns in addition to ``vertex``. This explicit alias mirrors the historical ``_header`` flag while keeping backward compatibility. .. py:property:: header :type: list[str] Return the headers of the cloud .. py:method:: get_vertices() -> list[wolfvertex] Return all vertices as a list. :return: list of :class:`wolfvertex` instances (references, not copies). .. py:method:: get_multipoint() -> shapely.geometry.MultiPoint Convert the cloud to a ``shapely.geometry.MultiPoint``. :return: MultiPoint object containing all vertices. .. py:method:: _updatebounds(newvert: wolfvertex = None, newcloud: dict = None) Update the bounds of the cloud :param newvert : (optional) vertex added to the cloud :param newcloud: (optional) cloud added to the cloud 'newvert' or 'newcloud' can be passed as argument during add_vertex operation. In this way, the bounds are updated without going through all the vertices -> expected more rapid. .. py:method:: find_minmax(force: bool = False) Compute the spatial bounds of the cloud. Updates ``xmin``, ``xmax``, ``ymin``, ``ymax``, ``zmin``, ``zmax`` as well as ``xbounds``, ``ybounds``, ``zbounds``. :param force: if ``True``, recompute from all coordinates. If ``False``, no action is taken (bounds are already up-to-date thanks to incremental updates from :meth:`_updatebounds`). .. py:method:: add_vertex(vertextoadd: wolfvertex = None, id=None, cloud: dict = None) Add one or more vertices to the cloud. Two usage modes: - *vertextoadd*: add a single vertex. If *id* is not provided, the identifier defaults to ``len(myvertices)``. - *cloud*: merge a dictionary ``{id: {'vertex': wolfvertex, ...}}`` into ``myvertices``. Existing keys are overwritten. Spatial bounds are updated incrementally. :param vertextoadd: single vertex to add. :param id: integer vertex identifier. ``None`` = auto-assigned. :param cloud: dictionary of vertices to merge. ``wolfvertex`` instances are referenced, not copied. .. py:method:: remove_vertex(id: int) Remove a vertex from the cloud and recompute bounds. :param id: integer identifier of the vertex to remove. A warning is logged if the identifier does not exist. .. py:method:: move_vertex(id: int, x: float, y: float, z: float | None = None, invalidate_tree: bool = True, notify: bool = True, recompute_bounds: bool = True) -> bool Move an existing vertex while preserving its row identifier. :param id: row identifier to move. :param x: new X coordinate. :param y: new Y coordinate. :param z: optional new Z coordinate. If ``None``, keeps current Z. :param invalidate_tree: if ``True``, clears KDTree cache. :param notify: if ``True``, calls :meth:`on_changed_vertices`. :param recompute_bounds: if ``True``, recomputes cloud bounds. :return: ``True`` if the vertex was moved, ``False`` otherwise. .. py:method:: remove_nearest_vertex(x: float, y: float, z: float = 0.0, max_distance: float | None = None) Remove the vertex closest to the given coordinates and recompute bounds. .. py:method:: remove_last_vertex() Remove the last added vertex (highest identifier) from the cloud and recompute bounds. .. py:method:: add_vertices(vertices: list[wolfvertex]) Add a list of vertices to the cloud. Identifiers are assigned sequentially starting from ``len(myvertices)``. :param vertices: list of :class:`wolfvertex` instances to add. .. py:method:: add_values_by_id_list(id: str, values: list[float]) Add values to the cloud :param id: use as key for the values :param values: list of values to be added - must be the same length as number of vertices .. py:method:: split_by_keys(keys: str | list[str], include_missing: bool = False) -> dict Split the cloud into sub-clouds grouped by one or several keys. Grouping keys are read from each row dictionary (same keys as :meth:`iter_rows`). For a single key, the returned mapping uses the scalar value as dictionary key. For multiple keys, it uses tuples. :param keys: one key name or a list of key names used for grouping. :param include_missing: if ``True``, rows missing at least one grouping key are still included with value ``None`` for missing entries. If ``False``, such rows are ignored. :return: ``{group_value: cloud_vertices}`` where each cloud contains only rows belonging to this group. .. py:method:: split_cloud(splitter, inside_prefix: str = 'inside_', outside_prefix: str = 'outside_') Split this cloud into inside/outside subsets using an external splitter. The *splitter* object is expected to provide either: - ``select_points_inside(cloud_vertices) -> list[bool]`` - or ``isinside(x, y) -> bool`` This duck-typed contract avoids importing vector classes here, preventing circular imports between PyVertex and pyvertexvectors. :param splitter: Geometry-like object used to classify points. :param inside_prefix: Prefix for the inside cloud identifier. :param outside_prefix: Prefix for the outside cloud identifier. :return: ``(cloud_inside, cloud_outside)``. .. py:method:: split_by_vector(vector_like, inside_prefix: str = 'inside_', outside_prefix: str = 'outside_') Explicit alias to split this cloud using a vector-like splitter. This is a convenience wrapper around :meth:`split_cloud` for the common case where the splitter is a vector object exposing ``select_points_inside`` and/or ``isinside``. :param vector_like: Vector-like object used to classify points. :param inside_prefix: Prefix for the inside cloud identifier. :param outside_prefix: Prefix for the outside cloud identifier. :return: ``(cloud_inside, cloud_outside)``. .. py:method:: set_legend_column(key: str, visible: bool = True) Set the legend to display a named value column for each point. :param key: Column name to display. Use ``''`` for the row identifier, ``'ID'`` for the sequential index, ``'X'`` / ``'Y'`` / ``'Z'`` for coordinates, or any column name previously added with :meth:`add_values_by_id_list`. :param visible: Whether to make the legend visible. Defaults to ``True``. .. py:method:: interp_on_array(myarray, key: str = 'vertex', method: Literal['linear', 'nearest', 'cubic'] = 'linear') Interpolation of the cloud on a 2D array :param myarray: WolfArray instance :param key: key to be used for the third column (Z) -- 'vertex' or any key in the dictionnary :param method: interpolation method -- 'linear', 'nearest', 'cubic' see interpolate_on_cloud method of WolfArray for more information .. py:method:: projectontrace(trace, return_cloud: bool = True, proximity: float = 99999.0) Project the cloud onto a trace (polyline of type ``vector``). Each point is orthogonally projected onto the trace; the curvilinear coordinate *s* (distance along the trace) and the original point's elevation *z* are extracted. :param trace: ``vector`` instance (must have ``asshapely_ls()`` and ``myname`` attributes). :param return_cloud: if ``True``, return a new ``cloud_vertices`` whose vertices are ``(s, z)``. If ``False``, return two lists ``(s_list, z_list)``. :param proximity: search radius around the trace (in map units). Only points within this buffer are kept. The default value (99999) keeps all points. :return: ``cloud_vertices`` or tuple ``(list[float], list[float])`` depending on *return_cloud*. .. py:class:: cloud_of_clouds(idx: str = '', clouds: list[cloud_vertices] | None = None) Collection of :class:`cloud_vertices` instances. Mirrors the ``Zones → zone → vector`` hierarchy but for point clouds: ``cloud_of_clouds → cloud_vertices → wolfvertex``. Provides: - cloud management (add, remove, reorder, access by index or name); - bulk display-property propagation (color, width, style, alpha, legend…); - value manipulation across all clouds (add, get, colorize); - iteration helpers; - spatial queries (bounds, nearest). :ivar myclouds: ordered list of :class:`cloud_vertices` instances. :ivar idx: text identifier for the collection. .. py:attribute:: myclouds :type: list[cloud_vertices] .. py:attribute:: idx :type: str .. py:attribute:: filename :value: None .. py:method:: add_cloud(cloud: cloud_vertices) -> None Append a cloud to the collection. :param cloud: cloud to add. .. py:method:: _make_cloud_vertices(**kwargs) -> cloud_vertices Create a cloud_vertices. GUI subclass returns the GUI variant. .. py:method:: _make_cloud_vertices_from_dict(d: dict, **kwargs) -> cloud_vertices Create a cloud_vertices from a dictionary. GUI subclass returns the GUI variant. .. py:method:: create_cloud(idx: str = '', **kwargs) -> cloud_vertices Create a new empty cloud and add it to the collection. :param idx: identifier for the new cloud. :param kwargs: forwarded to :meth:`_make_cloud_vertices`. :return: the newly created cloud. .. py:method:: remove_cloud(key: int | str) -> cloud_vertices | None Remove a cloud by index or name. :param key: integer index or string ``idx`` of the cloud. :return: the removed cloud, or ``None`` if not found. .. py:method:: _resolve(key: int | str) -> cloud_vertices | None Resolve a cloud by index or name. :param key: integer index or string ``idx``. :return: cloud instance, or ``None`` if not found. .. py:property:: nbclouds :type: int Number of clouds in the collection. .. py:property:: cloud_names :type: list[str] List of cloud identifiers. .. py:property:: nbvertices :type: int Total number of vertices across all clouds. .. py:method:: find_minmax(force: bool = True) Recompute bounds for all clouds. :param force: forwarded to each cloud's :meth:`find_minmax`. .. py:property:: xbounds :type: tuple[float, float] Global X extent across all clouds. .. py:property:: ybounds :type: tuple[float, float] Global Y extent across all clouds. .. py:property:: zbounds :type: tuple[float, float] Global Z extent across all clouds. .. py:method:: iter_all_vertices() Yield every vertex across all clouds. :yields: :class:`wolfvertex` instances. .. py:method:: iter_all_rows() Yield ``(cloud_idx, row_id, row_dict)`` for every row across all clouds. :yields: ``(str, row_id, dict)`` tuples. .. py:method:: add_values(key: str, values: numpy.ndarray | dict) Add values to the clouds. :param key: value column identifier. :param values: either a dict ``{cloud_idx: list}`` mapping cloud names to per-vertex value lists, or a flat ndarray whose length must equal :pyattr:`nbvertices` (values are distributed to clouds in order). .. py:method:: get_values(key: str) -> dict[str, numpy.ndarray] Retrieve values from all clouds. :param key: value column identifier. :return: dict ``{cloud_idx: ndarray}`` for clouds that have the key. .. py:method:: get_all_xyz() -> numpy.ndarray Return all XYZ coordinates as a single array. :return: ``(N, 3)`` array with all vertices concatenated. .. py:method:: set_color(color: int) -> None Set uniform drawing color for all clouds. :param color: RGB integer (see ``getIfromRGB``). .. py:method:: set_width(width: int) -> None Set point size for all clouds. :param width: size in pixels. .. py:method:: set_style(style: int) -> None Set rendering style for all clouds. :param style: style index (see ``Cloud_Styles``). .. py:method:: set_alpha(alpha: int) -> None Set transparency for all clouds. :param alpha: opacity value (0 = opaque, 255 = fully transparent). .. py:method:: set_filled(filled: bool) -> None Set symbol fill for all clouds. :param filled: ``True`` = filled symbols. .. py:method:: set_legend_visible(visible: bool = True) -> None Show or hide legends for all clouds. :param visible: ``True`` to display. .. py:method:: set_legend_text(text: str) -> None Set legend text for all clouds. :param text: legend text. .. py:method:: set_legend_color(color: int) -> None Set legend text color for all clouds. :param color: RGB integer. .. py:method:: set_legend_fontsize(size: int) -> None Set legend font size for all clouds. :param size: font size in points. .. py:method:: set_legend_from_idx(visible: bool = True) -> None Set each cloud's legend text to its own ``idx``. :param visible: whether to make legends visible. .. py:method:: find_nearest(xyz: numpy.ndarray | list, nb: int = 1) Find the nearest vertex across all clouds. Queries each cloud's KDTree and returns the overall nearest. :param xyz: query coordinates ``[x, y, z]`` or ``[[x, y, z], ...]``. :param nb: number of nearest neighbors. :return: ``(distance, wolfvertex, row_dict, cloud_idx)`` for the closest result, or ``(None, None, None, None)`` if empty. .. py:method:: merge(idx: str = '') -> cloud_vertices Merge all clouds into a single cloud. Vertex values are preserved. Cloud origin is tracked via a ``'__source__'`` value column. :param idx: identifier for the merged cloud. :return: new :class:`cloud_vertices` containing all vertices. .. py:method:: to_dict() -> dict Serialize the collection to a plain dictionary. Each child cloud is serialized via its own :meth:`cloud_vertices.to_dict`. .. py:method:: from_dict(d: dict, **kwargs) -> cloud_of_clouds :classmethod: Create a :class:`cloud_of_clouds` from a dictionary. :param d: dictionary as produced by :meth:`to_dict`. :param kwargs: forwarded to each child's :meth:`cloud_vertices.from_dict`. :return: new :class:`cloud_of_clouds` instance. .. py:method:: save_json(fn: str | pathlib.Path, indent: int = 2) -> None Save the collection to a JSON file. :param fn: destination file path. :param indent: JSON indentation level (``None`` for compact output). .. py:method:: load_json(fn: str | pathlib.Path, **kwargs) -> cloud_of_clouds :classmethod: Load a collection from a JSON file. :param fn: source file path. :param kwargs: forwarded to each child's :meth:`cloud_vertices.from_dict`. :return: new :class:`cloud_of_clouds` instance. :raises ValueError: if the file format is not ``cloud_of_clouds``. .. py:method:: duplicate(idx: str | None = None, **kwargs) -> cloud_of_clouds Create a deep copy of this collection and all its clouds. Every child cloud is duplicated independently; the new collection shares no mutable state with the original. :param idx: identifier for the copy. If ``None``, the original ``idx`` is reused. :param kwargs: extra keyword arguments forwarded to each child's constructor (e.g. ``mapviewer`` for the GUI). :return: independent :class:`cloud_of_clouds` copy. .. py:method:: copy(idx: str | None = None, **kwargs) -> cloud_of_clouds Alias to :meth:`duplicate`.