From aad403f2a6796cd67a169f6f49b13ac65b9c4ad2 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Thu, 23 Jan 2025 14:36:17 +0000 Subject: [PATCH 01/19] Initial config. --- .pre-commit-config.yaml | 5 +++++ pyproject.toml | 13 +++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2e8270a8..eee67177 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,11 @@ repos: types_or: [python, markdown, rst] additional_dependencies: [tomli] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v1.14.1' + hooks: + - id: mypy + - repo: https://github.com/numpy/numpydoc rev: v1.8.0 hooks: diff --git a/pyproject.toml b/pyproject.toml index c2585887..3fa0aca2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,15 @@ ignore-words-list = "whet,projets" skip = ".git,*.css,*.ipynb,*.js,*.html,*.map,*.po,CODE_OF_CONDUCT.md" +[tool.mypy] +strict = false +ignore_missing_imports = true +warn_unused_configs = true +warn_unreachable = true +enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] +exclude = [] + + [tool.numpydoc_validation] checks = [ "all", # Enable all numpydoc validation rules, apart from the following: @@ -123,10 +132,6 @@ xfail_strict = "True" [tool.repo-review] ignore = [ - # https://learn.scientific-python.org/development/guides/style/#MY100 - "MY100", # Uses MyPy (pyproject config). - # https://learn.scientific-python.org/development/guides/style/#PC140 - "PC140", # Uses mypy. # https://learn.scientific-python.org/development/guides/style/#PC180 "PC180", # Uses prettier. ] From c24243b4ad836aa93c77a525e79581cd5fa0a321 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Thu, 23 Jan 2025 15:34:35 +0000 Subject: [PATCH 02/19] Better config. --- .pre-commit-config.yaml | 2 ++ pyproject.toml | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eee67177..7d2c432d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,6 +39,8 @@ repos: rev: 'v1.14.1' hooks: - id: mypy + # https://github.com/python/mypy/issues/13916 + pass_filenames: false - repo: https://github.com/numpy/numpydoc rev: v1.8.0 diff --git a/pyproject.toml b/pyproject.toml index 3fa0aca2..302c47f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,12 +59,11 @@ skip = ".git,*.css,*.ipynb,*.js,*.html,*.map,*.po,CODE_OF_CONDUCT.md" [tool.mypy] -strict = false -ignore_missing_imports = true -warn_unused_configs = true -warn_unreachable = true +strict = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] exclude = [] +files = ["src/geovista"] +warn_unreachable = true [tool.numpydoc_validation] From 529186c2f8a36222d3a01bba6bdb5cd8566b249f Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 24 Jan 2025 18:32:01 +0000 Subject: [PATCH 03/19] Various MyPy fixes. --- pyproject.toml | 9 +++++++ src/geovista/__init__.pyi | 7 +++++ src/geovista/cli.py | 26 ++++++++++--------- src/geovista/config.py | 2 +- .../examples/spatial_index/uber_h3.py | 9 ++++--- src/geovista/geometry.py | 15 +++++++---- src/geovista/geoplotter.py | 15 ++++++++--- 7 files changed, 58 insertions(+), 25 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 302c47f1..93e61459 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,10 +60,19 @@ skip = ".git,*.css,*.ipynb,*.js,*.html,*.map,*.po,CODE_OF_CONDUCT.md" [tool.mypy] strict = true +# TODO: fix pyvistaqt +disallow_subclassing_any = false +# TODO: fix click? +disallow_untyped_decorators = false enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] exclude = [] files = ["src/geovista"] warn_unreachable = true +# +# +#[[tool.mypy.overrides]] +#module = "click" +#no_implicit_reexport = false [tool.numpydoc_validation] diff --git a/src/geovista/__init__.pyi b/src/geovista/__init__.pyi index 33c5c761..05305b67 100644 --- a/src/geovista/__init__.pyi +++ b/src/geovista/__init__.pyi @@ -10,8 +10,15 @@ from .pantry.textures import ( ) from .report import Report + +__version__: str +GEOVISTA_IMAGE_TESTING: bool + + __all__ = [ + "__version__", "GeoPlotter", + "GEOVISTA_IMAGE_TESTING", "Report", "Transform", "black_marble", diff --git a/src/geovista/cli.py b/src/geovista/cli.py index 28b8a35f..1fff9a74 100644 --- a/src/geovista/cli.py +++ b/src/geovista/cli.py @@ -85,23 +85,25 @@ def _download_group( if fg_colour is None: fg_colour = FG_COLOUR - name: str = "" if name is None else f"{name} " + name_post: str = "" if name is None else f"{name} " n_fnames: int = len(fnames) width: int = len(str(n_fnames)) previous = pooch_mute(silent=True) - click.echo(f"Downloading {n_fnames} {name}registered asset{_plural(n_fnames)}:") + click.echo(f"Downloading {n_fnames} {name_post}registered asset{_plural(n_fnames)}:") for i, fname in enumerate(fnames): click.echo(f"[{i + 1:0{width}d}/{n_fnames}] Downloading ", nl=False) click.secho(f"{fname} ", nl=False, fg=fg_colour) click.echo("... ", nl=False) processor = None - name = pathlib.Path(fname) - if decompress and (suffix := name.suffix) in pooch.Decompress.extensions: - name = name.stem.removesuffix(suffix) - processor = pooch.Decompress(method="auto", name=name) + name_path = pathlib.Path(fname) + if decompress and (suffix := name_path.suffix) in pooch.Decompress.extensions: + processor = pooch.Decompress( + method="auto", + name=name_path.stem.removesuffix(suffix) + ) CACHE.fetch(fname, processor=processor) click.secho("done!", fg="green") @@ -465,10 +467,10 @@ def examples( if groups: click.echo("Available example groups:") - groups = _groups() - n_groups = len(groups) + groups_new = _groups() + n_groups = len(groups_new) width = len(str(n_groups)) - for i, group in enumerate(groups): + for i, group in enumerate(groups_new): click.echo(f"[{i + 1:0{width}d}/{n_groups}] ", nl=False) click.secho(f"{group}", fg="green") click.echo("\nšŸ‘ All done!") @@ -491,9 +493,9 @@ def examples( return if run_group: - group = [script for script in EXAMPLES[1:] if script.startswith(run_group)] - n_group = len(group) - for i, script in enumerate(group): + filtered = [script for script in EXAMPLES[1:] if script.startswith(run_group)] + n_group = len(filtered) + for i, script in enumerate(filtered): msg = f"Running {run_group!r} example {script!r} ({i + 1} of {n_group}) ..." click.secho(msg, fg="green") module = importlib.import_module(f"geovista.examples.{script}") diff --git a/src/geovista/config.py b/src/geovista/config.py index f087b416..594e53af 100644 --- a/src/geovista/config.py +++ b/src/geovista/config.py @@ -48,7 +48,7 @@ def update_config(resources: dict[str, str]) -> None: # see https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html -resources: dict = { +resources: dict[str, Path] = { "cache_dir": Path(environ.get("XDG_CACHE_HOME", user_cache_dir())) / __package__ } """Resources configuration dictionary.""" diff --git a/src/geovista/examples/spatial_index/uber_h3.py b/src/geovista/examples/spatial_index/uber_h3.py index a80f32fe..6ac8ee3e 100755 --- a/src/geovista/examples/spatial_index/uber_h3.py +++ b/src/geovista/examples/spatial_index/uber_h3.py @@ -90,6 +90,7 @@ from dataclasses import dataclass from functools import partial from itertools import combinations +from typing import TypeAlias try: import h3 @@ -119,7 +120,7 @@ MDI = -1 #: Type alias -H3AssetLike = str | PolyData +H3AssetLike: TypeAlias = str | PolyData H3Indexes = set[str] @@ -276,7 +277,7 @@ def to_children(h3indexes: H3Indexes) -> H3Indexes: The children cell indexes. """ - result = set() + result: set[str] = set() children = [h3.cell_to_children(h3index) for h3index in h3indexes] result.update(*children) @@ -309,7 +310,9 @@ def to_mesh(h3indexes: H3Indexes) -> PolyData: The H3 mesh surface. """ - nverts, lats, lons = [], [], [] + nverts: list[int] = [] + lats: list[float] = [] + lons: list[float] = [] # Get the lat/lon vertices of each H3Index cell polygon. for h3index in h3indexes: diff --git a/src/geovista/geometry.py b/src/geovista/geometry.py index 9e9078c9..af8e522d 100644 --- a/src/geovista/geometry.py +++ b/src/geovista/geometry.py @@ -34,7 +34,10 @@ if TYPE_CHECKING: from collections.abc import Generator + import numpy as np + import pyvista as pv from shapely import LineString, MultiLineString + from shapely.geometry.base import GeometrySequence # lazy import third-party dependencies np = lazy.load("numpy") @@ -139,7 +142,9 @@ def load_coastline_geometries( fname = shp.natural_earth(resolution=resolution, category=category, name=name) reader = shp.Reader(fname) - def unpack(geometries: Generator[LineString | MultiLineString]) -> None: + def unpack( + geometries: Generator[LineString | MultiLineString] | list[GeometrySequence] + ) -> None: """Unpack the geometries coordinates. Parameters @@ -211,10 +216,10 @@ def load_coastlines( zlevel = 0 if zlevel is None else int(zlevel) radius += radius * zlevel * zscale - geoms = load_coastline_geometries(resolution=resolution) - npoints_per_geom = [geom.shape[0] for geom in geoms] - ngeoms = len(geoms) - geoms = np.concatenate(geoms) + geoms_list = load_coastline_geometries(resolution=resolution) + npoints_per_geom = [geom.shape[0] for geom in geoms_list] + ngeoms = len(geoms_list) + geoms = np.concatenate(geoms_list) nlines = geoms.shape[0] - ngeoms geoms = to_cartesian(geoms[:, 0], geoms[:, 1], radius=radius) diff --git a/src/geovista/geoplotter.py b/src/geovista/geoplotter.py index 0df763f5..7e8a645a 100644 --- a/src/geovista/geoplotter.py +++ b/src/geovista/geoplotter.py @@ -15,6 +15,7 @@ from __future__ import annotations +from collections.abc import Callable, Iterable from functools import lru_cache import os from typing import TYPE_CHECKING, Any @@ -87,10 +88,10 @@ "GeoPlotterBase", ] -ADD_POINTS_STYLE: list[str, str] = ["points", "points_gaussian"] +ADD_POINTS_STYLE: tuple[str, str] = ("points", "points_gaussian") """The valid 'style' options for adding points.""" -BASE_ZLEVEL_SCALE: int = 1.0e-3 +BASE_ZLEVEL_SCALE: float = 1.0e-3 """Proportional multiplier for z-axis levels/offsets of base-layer mesh.""" COASTLINES_RTOL: float = 1.0e-8 @@ -199,7 +200,7 @@ def __init__( # status of gpu opacity support self._missing_opacity = False # cartesian (xyz) center of last mesh added to the plotter - self._poi = None + self._poi: list[float] | None = None super().__init__(*args, **kwargs) def _add_graticule_labels( @@ -270,6 +271,7 @@ def _add_graticule_labels( # opinionated over-ride to disable label visibility filter point_labels_args["always_visible"] = False + self.add_point_labels: Callable[..., None] self.add_point_labels(xyz, graticule.labels, **point_labels_args) def _warn_opacity(self) -> None: @@ -288,6 +290,7 @@ def _warn_opacity(self) -> None: renderer_version = info.renderer, info.version if renderer_version in OPACITY_BLACKLIST: + self.add_text: Callable[..., None] self.add_text( "Requires GPU opacity support", position="lower_right", @@ -701,7 +704,7 @@ def _check(option: str) -> bool: # POI cartesian xyz self._poi = mesh.center - return super().add_mesh(mesh, **kwargs) + return super().add_mesh(mesh, **kwargs) # type: ignore[misc] def add_meridian( self, @@ -1276,6 +1279,7 @@ def view_poi( >>> p.show() """ + self.camera: pv.Plotter.camera camera = self.camera if crs is None: @@ -1307,6 +1311,8 @@ def view_poi( x, y, _ = self._poi crs = self.crs + # Assertion to appease MyPy. + assert x is not None and y is not None if crs != self.crs: x, y, _ = transform_point(crs, self.crs, x, y) @@ -1318,6 +1324,7 @@ def view_poi( u_hat = xyz / np.linalg.norm(xyz) # set the new camera position at the same magnitude from the focal point camera.position = u_hat * np.linalg.norm(camera.position) + self.reset_camera: Callable[..., None] self.reset_camera(render=False) clip = camera.clipping_range # defensive: extend far clip range to ensure no accidental From bc6985c53e6d68a848e02e2e60d91e06b840db90 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Mon, 27 Jan 2025 14:18:36 +0000 Subject: [PATCH 04/19] More MyPy fixes. --- src/geovista/bridge.py | 31 +++++++++++---------------- src/geovista/cache/__init__.py | 24 +++++++++++---------- src/geovista/common.py | 8 +++---- src/geovista/core.py | 9 ++++---- src/geovista/pantry/data.py | 38 +++++++++++++++++---------------- src/geovista/pantry/textures.py | 4 ++-- src/geovista/search.py | 14 ++++++++---- 7 files changed, 67 insertions(+), 61 deletions(-) diff --git a/src/geovista/bridge.py b/src/geovista/bridge.py index 4a4cbde0..c17278ed 100644 --- a/src/geovista/bridge.py +++ b/src/geovista/bridge.py @@ -64,7 +64,7 @@ PathLike: TypeAlias = str | PurePath """Type alias for an asset file path.""" -Shape: TypeAlias = tuple[int] +Shape: TypeAlias = tuple[int, ...] """Type alias for a tuple of integers.""" # constants @@ -409,7 +409,7 @@ def from_1d( data: ArrayLike | None = None, name: str | None = None, crs: CRSLike | None = None, - rgb: bool | None = False, + rgb: bool = False, radius: float | None = None, zlevel: int | None = None, zscale: float | None = None, @@ -500,7 +500,7 @@ def from_2d( data: ArrayLike | None = None, name: str | None = None, crs: CRSLike | None = None, - rgb: bool | None = False, + rgb: bool = False, radius: float | None = None, zlevel: int | None = None, zscale: float | None = None, @@ -573,6 +573,7 @@ def from_2d( cls._verify_2d(xs, ys) shape, ndim = xs.shape, xs.ndim + points_shape: tuple[int, int] if ndim == 2: # we have shape (M+1, N+1) rows, cols = points_shape = shape @@ -702,8 +703,6 @@ def from_points( if not name: name = NAME_POINTS - if not isinstance(name, str): - name = str(name) mesh.field_data[GV_FIELD_NAME] = np.array([name]) mesh[name] = data @@ -717,10 +716,10 @@ def from_points( @classmethod def from_tiff( cls, - fname: PathLike, + fname: Path, name: str | None = None, - band: int | None = 1, - rgb: bool | None = False, + band: int = 1, + rgb: bool = False, sieve: bool | None = False, size: int | None = None, extract: bool | None = False, @@ -736,16 +735,16 @@ def from_tiff( Parameters ---------- - fname : PathLike + fname : Path The file path to the GeoTIFF. name : str, optional The name of the GeoTIFF data array to be attached to the mesh. Defaults to :data:`NAME_POINTS`. Note that, ``{units}`` may be used as a placeholder for the units of the data array e.g., ``"Elevation / {units}"``. - band : int, optional + band : int, default=1 The band index to read from the GeoTIFF. Note that, the `band` - index is one-based. Defaults to the first band i.e., ``band=1``. + index is one-based. rgb : bool, default=False Specify whether to read the GeoTIFF as an ``RGB`` or ``RGBA`` image. When ``rgb=True``, the `band` index is ignored. @@ -816,11 +815,8 @@ def from_tiff( ) raise ImportError(emsg) from None - if isinstance(fname, str): - fname = Path(fname) - fname = fname.resolve(strict=True) - band = None if rgb else band + band = -1 if rgb else band if size is None: size = RIO_SIEVE_SIZE @@ -926,7 +922,7 @@ def from_unstructured( start_index: int | None = None, name: str | None = None, crs: CRSLike | None = None, - rgb: bool | None = None, + rgb: bool = False, radius: float | None = None, zlevel: int | None = None, zscale: float | None = None, @@ -1154,8 +1150,7 @@ def from_unstructured( if not name: size = data.size // data.shape[-1] if rgb else data.size name = NAME_POINTS if size == mesh.n_points else NAME_CELLS - if not isinstance(name, str): - name = str(name) + assert isinstance(name, str) mesh.field_data[GV_FIELD_NAME] = np.array([name]) mesh[name] = data diff --git a/src/geovista/cache/__init__.py b/src/geovista/cache/__init__.py index 3860c037..adaa869c 100644 --- a/src/geovista/cache/__init__.py +++ b/src/geovista/cache/__init__.py @@ -16,7 +16,7 @@ from functools import wraps import os from pathlib import Path -from typing import IO, TYPE_CHECKING, TypeAlias +from typing import Any, AnyStr, IO, TYPE_CHECKING, TypeAlias import pooch @@ -38,7 +38,7 @@ from collections.abc import Callable # type aliases -FileLike: TypeAlias = str | IO +FileLike: TypeAlias = str | IO[AnyStr] """Type alias for filename or file-like object.""" BASE_URL: str = "https://github.com/bjlittle/geovista-data/raw/{version}/assets/" @@ -94,10 +94,12 @@ @wraps(CACHE._fetch) # noqa: SLF001 -def _fetch(*args: str, **kwargs: bool | Callable) -> str: # numpydoc ignore=GL08 +def _fetch(*args: str, **kwargs: bool | Callable[..., Any]) -> str: # numpydoc ignore=GL08 # default to our http/s downloader with user-agent headers kwargs.setdefault("downloader", _downloader) - return CACHE._fetch(*args, **kwargs) # noqa: SLF001 + result = CACHE._fetch(*args, **kwargs) # noqa: SLF001 + assert isinstance(result, str) + return result # override the original Pooch.fetch method with our @@ -107,7 +109,7 @@ def _fetch(*args: str, **kwargs: bool | Callable) -> str: # numpydoc ignore=GL0 def _downloader( url: str, - output_file: FileLike, + output_file: FileLike[Any], poocher: pooch.Pooch, check_only: bool | None = False, ) -> bool | None: @@ -144,20 +146,20 @@ def _downloader( # see https://github.com/readthedocs/readthedocs.org/issues/11763 headers = {"User-Agent": f"geovista ({geovista.__version__})"} - downloader = pooch.HTTPDownloader(headers=headers) + downloader: Callable[..., bool | None] = pooch.HTTPDownloader(headers=headers) return downloader(url, output_file, poocher, check_only=check_only) -def pooch_mute(silent: bool | None = True) -> bool: +def pooch_mute(silent: bool = True) -> bool: """Control the :mod:`pooch` cache manager logger verbosity. Updates the status variable :data:`GEOVISTA_POOCH_MUTE`. Parameters ---------- - silent : bool, optional + silent : bool, default=True Whether to silence or activate the :mod:`pooch` cache manager logger messages - to the console. Defaults to ``True``. + to the console. Returns ------- @@ -195,10 +197,10 @@ def reload_registry(fname: str | None = None) -> None: """ if fname is None: - fname = (Path(__file__).parent / "registry.txt").open( + text_io = (Path(__file__).parent / "registry.txt").open( "r", encoding="utf-8", errors="strict" ) - CACHE.load_registry(fname) + CACHE.load_registry(text_io) # configure the pooch cache manager logger verbosity diff --git a/src/geovista/common.py b/src/geovista/common.py index de013491..9fed4785 100644 --- a/src/geovista/common.py +++ b/src/geovista/common.py @@ -158,12 +158,12 @@ class MixinStrEnum: """ @classmethod - def _missing_(cls, item: str | Preference) -> Preference | None: + def _missing_(cls, value: str | Preference) -> Preference | None: """Handle missing enumeration members. Parameters ---------- - item : str or Preference + value : str or Preference The candidate preference enumeration member. Returns @@ -177,9 +177,9 @@ def _missing_(cls, item: str | Preference) -> Preference | None: .. versionadded:: 0.3.0 """ - item = str(item).lower() + value_string = str(value).lower() for member in cls: - if member.value == item: + if member.value == value_string: return member return None diff --git a/src/geovista/core.py b/src/geovista/core.py index a3612deb..2cc40018 100644 --- a/src/geovista/core.py +++ b/src/geovista/core.py @@ -322,7 +322,7 @@ def add_texture_coords( def combine( - *meshes: Iterable[pv.PolyData], + *meshes: pv.PolyData, data: bool | None = True, clean: bool | None = False, ) -> pv.PolyData: @@ -549,7 +549,7 @@ def resize( ) else: new_radius = radius + radius * zlevel * zscale - update = new_radius and not np.isclose(distance(mesh), new_radius) + update = new_radius is not None and not np.isclose(distance(mesh), new_radius) if update: lonlat = from_cartesian(mesh) @@ -628,6 +628,7 @@ def slice_cells( meridian += 180 meridian = wrap(meridian)[0] + assert isinstance(meridian, float) info = mesh.active_scalars_info slicer = MeridianSlice(mesh, meridian) @@ -673,8 +674,8 @@ def slice_cells( cxpts = neighbours.get_cell(cid).points[:, 0] cxmin, cxmax = cxpts.min(), cxpts.max() xdelta.append(cxmax - cxmin) - xdelta = np.array(xdelta) - bad = np.where(xdelta > 270)[0] + xdelta_array = np.array(xdelta) + bad = np.where(xdelta_array > 270)[0] if bad.size: bad_cids = np.unique(neighbours[GV_CELL_IDS][bad]) plural = "s" if (n_cells := bad_cids.size) > 1 else "" diff --git a/src/geovista/pantry/data.py b/src/geovista/pantry/data.py index 16d23faf..2024e8c3 100644 --- a/src/geovista/pantry/data.py +++ b/src/geovista/pantry/data.py @@ -68,7 +68,9 @@ """The registry key for the pantry data.""" -class CloudPreference(MixinStrEnum, StrEnum): +# Type ignore because we type-hint MixinStrEnum - a good thing - but this +# makes it inconsistent with StrEnum. +class CloudPreference(MixinStrEnum, StrEnum): # type: ignore[misc] """Enumeration of mesh types for cloud amount. Notes @@ -90,10 +92,10 @@ class SampleStructuredXY: lons: ArrayLike lats: ArrayLike - data: ArrayLike = field(default=None) - name: str = field(default=None) - units: str = field(default=None) - steps: int = field(default=None) + data: ArrayLike | None = field(default=None) + name: str | None = field(default=None) + units: str | None = field(default=None) + steps: int | None = field(default=None) ndim: int = 2 @@ -104,10 +106,10 @@ class SampleStructuredXYZ: lons: ArrayLike lats: ArrayLike zlevel: ArrayLike - data: ArrayLike = field(default=None) - name: str = field(default=None) - units: str = field(default=None) - steps: int = field(default=None) + data: ArrayLike | None = field(default=None) + name: str | None = field(default=None) + units: str | None = field(default=None) + steps: int | None = field(default=None) ndim: int = 3 @@ -118,13 +120,13 @@ class SampleUnstructuredXY: lons: ArrayLike lats: ArrayLike connectivity: ArrayLike - data: ArrayLike = field(default=None) - face: ArrayLike = field(default=None) - node: ArrayLike = field(default=None) - start_index: int = field(default=None) - name: str = field(default=None) - units: str = field(default=None) - steps: int = field(default=None) + data: ArrayLike | None = field(default=None) + face: ArrayLike | None = field(default=None) + node: ArrayLike | None = field(default=None) + start_index: int | None = field(default=None) + name: str | None = field(default=None) + units: str | None = field(default=None) + steps: int | None = field(default=None) ndim: int = 2 @@ -147,9 +149,9 @@ def capitalise(title: str) -> str: """ title = title.replace("_", " ") - title = title.split(" ") + title_list = title.split(" ") - return " ".join([word.capitalize() for word in title]) + return " ".join([word.capitalize() for word in title_list]) def _cloud_amount_dataset(fname: str | CloudPreference) -> nc.Dataset: diff --git a/src/geovista/pantry/textures.py b/src/geovista/pantry/textures.py index b36aa46b..e1b84e79 100644 --- a/src/geovista/pantry/textures.py +++ b/src/geovista/pantry/textures.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TypeAlias, TYPE_CHECKING import lazy_loader as lazy @@ -23,7 +23,7 @@ import pyvista as pv # type aliases - TextureLike = str | pv.Texture + TextureLike: TypeAlias = str | pv.Texture # lazy import third-party dependencies pv = lazy.load("pyvista") diff --git a/src/geovista/search.py b/src/geovista/search.py index 0a3598a4..b392d081 100644 --- a/src/geovista/search.py +++ b/src/geovista/search.py @@ -64,7 +64,9 @@ """The default search preference.""" -class SearchPreference(MixinStrEnum, StrEnum): +# Type ignore because we type-hint MixinStrEnum - a good thing - but this +# makes it inconsistent with StrEnum. +class SearchPreference(MixinStrEnum, StrEnum): # type: ignore[misc] """Enumeration of mesh geometry search preferences. Notes @@ -158,7 +160,7 @@ def __init__( # TODO @bjlittle: Clarify zlevel preservation for non-WGS84 point-clouds. xyz = to_cartesian(transformed[:, 0], transformed[:, 1]) - self._n_points = xyz.shape[0] + self._n_points: int = xyz.shape[0] self._mesh_type = mesh.__class__.__name__ self._kdtree = pyKDTree(xyz, leafsize=leaf_size) @@ -199,7 +201,9 @@ def leaf_size(self) -> int: .. versionadded:: 0.3.0 """ - return self._kdtree.leafsize + result = self._kdtree.leafsize + assert isinstance(result, int) + return result @property def n_points(self) -> int: @@ -310,9 +314,11 @@ def query( xyz = to_cartesian(lons, lats, radius=radius, zlevel=zlevel, zscale=zscale) - return self._kdtree.query( + result = self._kdtree.query( xyz, k=k, eps=epsilon, distance_upper_bound=distance_upper_bound ) + assert isinstance(result, tuple) and len(result) == 2 + return result def find_cell_neighbours(mesh: pv.PolyData, cid: CellIDLike) -> CellIDs: From ba64af69ccfe99dbd7a0d614c225acd7e6539b7d Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Tue, 28 Jan 2025 13:24:55 +0000 Subject: [PATCH 05/19] More MyPy fixes. --- src/geovista/common.py | 18 ++++++++---- src/geovista/crs.py | 9 +++--- src/geovista/geodesic.py | 60 ++++++++++++++++++++++----------------- src/geovista/gridlines.py | 6 ---- 4 files changed, 51 insertions(+), 42 deletions(-) diff --git a/src/geovista/common.py b/src/geovista/common.py index 9fed4785..87065672 100644 --- a/src/geovista/common.py +++ b/src/geovista/common.py @@ -180,7 +180,8 @@ def _missing_(cls, value: str | Preference) -> Preference | None: value_string = str(value).lower() for member in cls: if member.value == value_string: - return member + result: Preference = member + return result return None @classmethod @@ -221,7 +222,9 @@ def values(cls) -> tuple[str, ...]: return tuple([member.value for member in cls]) -class Preference(MixinStrEnum, StrEnum): +# Type ignore because we type-hint MixinStrEnum - a good thing - but this +# makes it inconsistent with StrEnum. +class Preference(MixinStrEnum, StrEnum): # type: ignore[misc] """Enumeration of common mesh geometry preferences. Notes @@ -536,7 +539,8 @@ def get_modules(root: str, base: bool | None = True) -> list[str]: .. versionadded:: 0.5.0 """ - modules, pkgs = [], [] + modules: list[str] = [] + pkgs: list[str] = [] for info in pkgutil.iter_modules(importlib.import_module(root).__path__): name = f"{root}.{info.name}" @@ -600,11 +604,12 @@ def point_cloud(mesh: pv.PolyData) -> bool: .. versionadded:: 0.2.0 """ - return (mesh.n_points == mesh.n_cells) and (mesh.n_lines == 0) + result: bool = (mesh.n_points == mesh.n_cells) and (mesh.n_lines == 0) + return result def sanitize_data( - *meshes: Iterable[pv.PolyData], + *meshes: pv.PolyData, ) -> None: """Purge standard VTK helper cell and point data index arrays. @@ -898,7 +903,8 @@ def triangulated(surface: pv.PolyData) -> bool: .. versionadded:: 0.1.0 """ - return np.all(np.diff(surface._offset_array) == 3) # noqa: SLF001 + result: bool = np.all(np.diff(surface._offset_array) == 3) # noqa: SLF001 + return result def vtk_warnings_off() -> None: diff --git a/src/geovista/crs.py b/src/geovista/crs.py index 747b05f0..2421dced 100644 --- a/src/geovista/crs.py +++ b/src/geovista/crs.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeAlias +from typing import Any, TYPE_CHECKING, TypeAlias import lazy_loader as lazy from pyproj import CRS @@ -39,7 +39,7 @@ ] # type aliases -CRSLike: TypeAlias = int | str | dict | CRS +CRSLike: TypeAlias = int | str | dict[str, Any] | CRS """Type alias for a Coordinate Reference System.""" # constants @@ -109,8 +109,8 @@ def get_central_meridian(crs: CRS) -> float | None: filter(lambda param: param.code == EPSG_CENTRAL_MERIDIAN, params) ) if len(cm_param) == 1: - (cm_param,) = cm_param - result = cm_param.value + (cm_param_single,) = cm_param + result = cm_param_single.value return result @@ -161,6 +161,7 @@ def projected(mesh: pv.PolyData) -> bool: """ crs = from_wkt(mesh) + result: bool if crs is None: xmin, xmax, ymin, ymax, zmin, zmax = mesh.bounds xdelta, ydelta, zdelta = (xmax - xmin), (ymax - ymin), (zmax - zmin) diff --git a/src/geovista/geodesic.py b/src/geovista/geodesic.py index 8d4ecbaf..7d23e57c 100644 --- a/src/geovista/geodesic.py +++ b/src/geovista/geodesic.py @@ -15,7 +15,7 @@ from collections.abc import Iterable from enum import StrEnum -from typing import TYPE_CHECKING, TypeAlias +from typing import Sequence, TYPE_CHECKING, TypeAlias import warnings import lazy_loader as lazy @@ -35,6 +35,7 @@ if TYPE_CHECKING: import numpy as np from numpy.typing import ArrayLike + import pyproj import pyvista as pv # lazy import third-party dependencies @@ -123,7 +124,9 @@ """The default bounding-box preference.""" -class EnclosedPreference(MixinStrEnum, StrEnum): +# Type ignore because we type-hint MixinStrEnum - a good thing - but this +# makes it inconsistent with StrEnum. +class EnclosedPreference(MixinStrEnum, StrEnum): # type: ignore[misc] """Enumeration of mesh geometry enclosed preferences. Notes @@ -145,7 +148,7 @@ def __init__( lons: ArrayLike, lats: ArrayLike, ellps: str | None = ELLIPSE, - c: int | None = BBOX_C, + c: int = BBOX_C, triangulate: bool | None = False, ) -> None: """Create 3-D geodesic bounding-box to extract enclosed mesh, lines or point. @@ -168,9 +171,8 @@ def __init__( ellps : str, optional The ellipsoid for geodesic calculations. See :func:`pyproj.list.get_ellps_map`. Defaults to :data:`ELLIPSE`. - c : float, optional + c : float, default=:data:`BBOX_C` The bounding-box face geometry will contain ``c**2`` cells. - Defaults to :data:`BBOX_C`. triangulate : bool, optional Specify whether the bounding-box faces are triangulated. Defaults to ``False``. @@ -231,12 +233,12 @@ def __init__( self.c = c self.triangulate = triangulate # the resultant bounding-box mesh - self._mesh = None + self._mesh: pv.PolyData | None = None # cache prior surface radius, as an optimisation - self._surface_radius = None + self._surface_radius: float | None = None - def __eq__(self, other: BBox) -> bool: - result = NotImplemented + def __eq__(self, other: object) -> bool: + result: bool = NotImplemented if isinstance(other, BBox): result = False lhs = (self.ellps, self.c, self.triangulate) @@ -247,7 +249,7 @@ def __eq__(self, other: BBox) -> bool: result = np.allclose(self.lats, other.lats) return result - def __ne__(self, other: BBox) -> bool: + def __ne__(self, other: object) -> bool: result = self == other if result is not NotImplemented: result = not result @@ -306,7 +308,8 @@ def _init(self) -> None: """ self._idx_map = np.empty((self.c + 1, self.c + 1), dtype=int) - self._bbox_lons, self._bbox_lats = [], [] + self._bbox_lons: list[float] = [] + self._bbox_lats: list[float] = [] self._bbox_count = 0 self._geod = pyproj.Geod(ellps=self.ellps) self._npts = self.c - 1 @@ -364,7 +367,7 @@ def _generate_bbox_face(self) -> None: # corner indices c1_idx, c2_idx, c3_idx, c4_idx = range(4) - def bbox_extend(lons: Iterable[float], lats: Iterable[float]) -> None: + def bbox_extend(lons: Sequence[float], lats: Sequence[float]) -> None: """Register the bounding box longitudes and latitudes. Parameters @@ -404,9 +407,14 @@ def bbox_update( """ assert row is not None or column is not None if row is None: - row = slice(None) + row_slice = slice(None) + else: + row_slice = slice(row, row + 1) if column is None: - column = slice(None) + column_slice = slice(None) + else: + column_slice = slice(column, column + 1) + glons, glats = npoints_by_idx( self._bbox_lons, self._bbox_lats, @@ -415,7 +423,7 @@ def bbox_update( npts=self._npts, geod=self._geod, ) - self._idx_map[row, column] = [ + self._idx_map[row_slice, column_slice] = [ idx1, *(np.arange(self._npts) + self._bbox_count), idx2, @@ -431,8 +439,8 @@ def bbox_update( # register bbox inner indices and points for row_idx in range(1, self.c): - row = self._idx_map[row_idx] - bbox_update(row[0], row[-1], row=row_idx) + row_idx_map = self._idx_map[row_idx] + bbox_update(row_idx_map[0], row_idx_map[-1], row=row_idx) def _generate_bbox_mesh( self, surface: pv.PolyData | None = None, radius: float | None = None @@ -595,6 +603,7 @@ def boundary( """ self._generate_bbox_mesh(surface=surface, radius=radius) + assert isinstance(self._surface_radius, float) radius = self._surface_radius + self._surface_radius * ZLEVEL_SCALE edge_idxs = self._bbox_face_edge_idxs() @@ -863,7 +872,8 @@ def line( ) raise ValueError(emsg) - line_lons, line_lats = [], [] + line_lons: list[float] = [] + line_lats: list[float] = [] geod = pyproj.Geod(ellps=ellps) for idx in range(n_lons - 1): @@ -1070,7 +1080,7 @@ def npoints_by_idx( def panel( name: int | str, ellps: str | None = ELLIPSE, - c: int | None = BBOX_C, + c: int = BBOX_C, triangulate: bool | None = False, ) -> BBox: """Create boundary-box for specific cubed-sphere panel. @@ -1084,9 +1094,8 @@ def panel( ellps : str, optional The ellipsoid for geodesic calculations. See :func:`pyproj.list.get_ellps_map`. Defaults to :data:`ELLIPSE`. - c : float, optional - The bounding-box face geometry will contain ``c**2`` cells. Defaults to - :data:`BBOX_C`. + c : float, default=:data:`BBOX_C` + The bounding-box face geometry will contain ``c**2`` cells. triangulate : bool, optional Specify whether the panel bounding-box faces are triangulated. Defaults to ``False``. @@ -1143,7 +1152,7 @@ def wedge( lon1: float, lon2: float, ellps: str | None = ELLIPSE, - c: int | None = BBOX_C, + c: int = BBOX_C, triangulate: bool | None = False, ) -> BBox: """Create geodesic bounding-box manifold wedge from the north to the south pole. @@ -1157,9 +1166,8 @@ def wedge( ellps : str, optional The ellipsoid for geodesic calculations. See :func:`pyproj.list.get_ellps_map`. Defaults to :data:`ELLIPSE`. - c : float, optional - The bounding-box face geometry will contain ``c**2`` cells. Defaults to - :data:`BBOX_C`. + c : float, default=:data:`BBOX_C` + The bounding-box face geometry will contain ``c**2`` cells. triangulate : bool, optional Specify whether the wedge bounding-box faces are triangulated. Defaults to ``False``. diff --git a/src/geovista/gridlines.py b/src/geovista/gridlines.py index 74b74898..6880530c 100644 --- a/src/geovista/gridlines.py +++ b/src/geovista/gridlines.py @@ -191,9 +191,6 @@ def create_meridian_labels(lons: list[float]) -> list[str]: """ result = [] - if not isinstance(lons, Iterable): - lons = [lons] - for lon in lons: # TODO @bjlittle: Explicit truncation is performed, perhaps offer format # control when required. @@ -397,9 +394,6 @@ def create_parallel_labels( if poles_parallel is None: poles_parallel = LATITUDE_POLES_PARALLEL - if not isinstance(lats, Iterable): - lats = [lats] - for lat in lats: # explicit truncation, perhaps offer format control when required value = int(lat) From 1a4a4d29eb5b85d3813d9a37dead8b8ba5f5689d Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 29 Jan 2025 12:26:59 +0000 Subject: [PATCH 06/19] Finalised MyPy. --- src/geovista/common.py | 30 +++++++++++++++--------------- src/geovista/geodesic.py | 21 ++++++--------------- src/geovista/pantry/data.py | 7 ++----- src/geovista/search.py | 10 ++++------ 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/geovista/common.py b/src/geovista/common.py index 87065672..264fa0b4 100644 --- a/src/geovista/common.py +++ b/src/geovista/common.py @@ -56,8 +56,8 @@ "WRAP_RTOL", "ZLEVEL_SCALE", "ZTRANSFORM_FACTOR", - "MixinStrEnum", "Preference", + "StrEnumPlus", "active_kernel", "cast_UnstructuredGrid_to_PolyData", "distance", @@ -148,28 +148,31 @@ """The zlevel scaling to be applied when transforming to a projection.""" -class MixinStrEnum: - """Convenience behaviour mixin for a string enumeration. +class StrEnumPlus(StrEnum): + """Convenience behaviour for a string enumeration. Notes ----- - .. versionadded:: 0.3.0 + .. versionadded:: 0.6.0 + + Previously called MixinStrEnum. """ @classmethod - def _missing_(cls, value: str | Preference) -> Preference | None: + def _missing_(cls, value: object) -> StrEnumPlus | None: """Handle missing enumeration members. Parameters ---------- - value : str or Preference - The candidate preference enumeration member. + value : object + The candidate preference enumeration member. Expected to be str or + StrEnumPlus instance, but could be any object. Returns ------- - Preference - The preference member or None if the member is not a valid + StrEnumPlus + The enum member or None if the member is not a valid enumeration member. Notes @@ -180,12 +183,11 @@ def _missing_(cls, value: str | Preference) -> Preference | None: value_string = str(value).lower() for member in cls: if member.value == value_string: - result: Preference = member - return result + return member return None @classmethod - def valid(cls, item: str | Preference) -> bool: + def valid(cls, item: str | StrEnumPlus) -> bool: """Determine whether the provided item is a valid enumeration member. Parameters @@ -222,9 +224,7 @@ def values(cls) -> tuple[str, ...]: return tuple([member.value for member in cls]) -# Type ignore because we type-hint MixinStrEnum - a good thing - but this -# makes it inconsistent with StrEnum. -class Preference(MixinStrEnum, StrEnum): # type: ignore[misc] +class Preference(StrEnumPlus): """Enumeration of common mesh geometry preferences. Notes diff --git a/src/geovista/geodesic.py b/src/geovista/geodesic.py index 7d23e57c..380dc331 100644 --- a/src/geovista/geodesic.py +++ b/src/geovista/geodesic.py @@ -13,9 +13,8 @@ from __future__ import annotations -from collections.abc import Iterable -from enum import StrEnum -from typing import Sequence, TYPE_CHECKING, TypeAlias +from collections.abc import Iterable, Sequence +from typing import TYPE_CHECKING, TypeAlias import warnings import lazy_loader as lazy @@ -24,7 +23,7 @@ GV_FIELD_RADIUS, RADIUS, ZLEVEL_SCALE, - MixinStrEnum, + StrEnumPlus, distance, to_cartesian, wrap, @@ -124,9 +123,7 @@ """The default bounding-box preference.""" -# Type ignore because we type-hint MixinStrEnum - a good thing - but this -# makes it inconsistent with StrEnum. -class EnclosedPreference(MixinStrEnum, StrEnum): # type: ignore[misc] +class EnclosedPreference(StrEnumPlus): """Enumeration of mesh geometry enclosed preferences. Notes @@ -406,14 +403,8 @@ def bbox_update( """ assert row is not None or column is not None - if row is None: - row_slice = slice(None) - else: - row_slice = slice(row, row + 1) - if column is None: - column_slice = slice(None) - else: - column_slice = slice(column, column + 1) + row_slice = slice(None) if row is None else slice(row, row + 1) + column_slice = slice(None) if column is None else slice(column, column + 1) glons, glats = npoints_by_idx( self._bbox_lons, diff --git a/src/geovista/pantry/data.py b/src/geovista/pantry/data.py index 2024e8c3..557e7789 100644 --- a/src/geovista/pantry/data.py +++ b/src/geovista/pantry/data.py @@ -14,14 +14,13 @@ from __future__ import annotations from dataclasses import dataclass, field -from enum import StrEnum from functools import lru_cache from typing import TYPE_CHECKING import lazy_loader as lazy from geovista.cache import CACHE -from geovista.common import LRU_CACHE_SIZE, MixinStrEnum +from geovista.common import LRU_CACHE_SIZE, StrEnumPlus if TYPE_CHECKING: import netCDF4 as nc # noqa: N813 @@ -68,9 +67,7 @@ """The registry key for the pantry data.""" -# Type ignore because we type-hint MixinStrEnum - a good thing - but this -# makes it inconsistent with StrEnum. -class CloudPreference(MixinStrEnum, StrEnum): # type: ignore[misc] +class CloudPreference(StrEnumPlus): """Enumeration of mesh types for cloud amount. Notes diff --git a/src/geovista/search.py b/src/geovista/search.py index b392d081..3dc0c0af 100644 --- a/src/geovista/search.py +++ b/src/geovista/search.py @@ -14,12 +14,11 @@ from __future__ import annotations from collections.abc import Iterable -from enum import StrEnum from typing import TYPE_CHECKING import lazy_loader as lazy -from .common import MixinStrEnum, to_cartesian +from .common import StrEnumPlus, to_cartesian from .crs import WGS84, from_wkt from .transform import transform_points @@ -64,9 +63,7 @@ """The default search preference.""" -# Type ignore because we type-hint MixinStrEnum - a good thing - but this -# makes it inconsistent with StrEnum. -class SearchPreference(MixinStrEnum, StrEnum): # type: ignore[misc] +class SearchPreference(StrEnumPlus): """Enumeration of mesh geometry search preferences. Notes @@ -317,7 +314,8 @@ def query( result = self._kdtree.query( xyz, k=k, eps=epsilon, distance_upper_bound=distance_upper_bound ) - assert isinstance(result, tuple) and len(result) == 2 + assert isinstance(result, tuple) + assert len(result) == 2 return result From 64b56d5c6b991cd60d43e88836e317c3724157b0 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 29 Jan 2025 14:06:51 +0000 Subject: [PATCH 07/19] Pre-commit fixes. --- src/geovista/bridge.py | 33 +++++++++++++++++++++------------ src/geovista/cache/__init__.py | 15 ++++++++++----- src/geovista/cli.py | 7 ++++--- src/geovista/core.py | 2 -- src/geovista/crs.py | 2 +- src/geovista/geometry.py | 2 +- src/geovista/geoplotter.py | 6 ++++-- src/geovista/gridlines.py | 1 - src/geovista/pantry/textures.py | 2 +- 9 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/geovista/bridge.py b/src/geovista/bridge.py index c17278ed..4df4de06 100644 --- a/src/geovista/bridge.py +++ b/src/geovista/bridge.py @@ -409,7 +409,7 @@ def from_1d( data: ArrayLike | None = None, name: str | None = None, crs: CRSLike | None = None, - rgb: bool = False, + rgb: bool | None = None, radius: float | None = None, zlevel: int | None = None, zscale: float | None = None, @@ -449,10 +449,10 @@ def from_1d( The Coordinate Reference System of the provided `xs` and `ys`. May be anything accepted by :meth:`pyproj.crs.CRS.from_user_input`. Defaults to ``EPSG:4326`` i.e., ``WGS 84``. - rgb : bool, default=False + rgb : bool, optional Whether `data` is an ``RGB`` or ``RGBA`` image. When ``rgb=True``, `data` is expected to have an extra dimension for the colour - channels (length ``3`` or ``4``). + channels (length ``3`` or ``4``). Defaults to ``False``. radius : float, optional The radius of the sphere. Defaults to :data:`~geovista.common.RADIUS`. zlevel : int, default=0 @@ -477,6 +477,9 @@ def from_1d( .. versionadded:: 0.1.0 """ + if rgb is None: + rgb = False + xs, ys = cls._as_contiguous_1d(xs, ys) mxs, mys = np.meshgrid(xs, ys, indexing="xy") return Transform.from_2d( @@ -500,7 +503,7 @@ def from_2d( data: ArrayLike | None = None, name: str | None = None, crs: CRSLike | None = None, - rgb: bool = False, + rgb: bool | None = None, radius: float | None = None, zlevel: int | None = None, zscale: float | None = None, @@ -541,10 +544,10 @@ def from_2d( The Coordinate Reference System of the provided `xs` and `ys`. May be anything accepted by :meth:`pyproj.crs.CRS.from_user_input`. Defaults to ``EPSG:4326`` i.e., ``WGS 84``. - rgb : bool, default=False + rgb : bool, optional Whether `data` is an ``RGB`` or ``RGBA`` image. When ``rgb=True``, `data` is expected to have an extra dimension for the colour - channels (length ``3`` or ``4``). + channels (length ``3`` or ``4``). Defaults to ``False``. radius : float, optional The radius of the sphere. Defaults to :data:`~geovista.common.RADIUS`. zlevel : int, default=0 @@ -719,7 +722,7 @@ def from_tiff( fname: Path, name: str | None = None, band: int = 1, - rgb: bool = False, + rgb: bool | None = None, sieve: bool | None = False, size: int | None = None, extract: bool | None = False, @@ -745,9 +748,9 @@ def from_tiff( band : int, default=1 The band index to read from the GeoTIFF. Note that, the `band` index is one-based. - rgb : bool, default=False + rgb : bool, optional Specify whether to read the GeoTIFF as an ``RGB`` or ``RGBA`` image. - When ``rgb=True``, the `band` index is ignored. + When ``rgb=True``, the `band` index is ignored. Defaults to ``False``. sieve : bool, default=False Specify whether to sieve the GeoTIFF mask to remove small connected regions. See :func:`rasterio.features.sieve` for more information. @@ -815,6 +818,9 @@ def from_tiff( ) raise ImportError(emsg) from None + if rgb is None: + rgb = False + fname = fname.resolve(strict=True) band = -1 if rgb else band @@ -922,7 +928,7 @@ def from_unstructured( start_index: int | None = None, name: str | None = None, crs: CRSLike | None = None, - rgb: bool = False, + rgb: bool | None = None, radius: float | None = None, zlevel: int | None = None, zscale: float | None = None, @@ -974,10 +980,10 @@ def from_unstructured( The Coordinate Reference System of the provided `xs` and `ys`. May be anything accepted by :meth:`pyproj.crs.CRS.from_user_input`. Defaults to ``EPSG:4326`` i.e., ``WGS 84``. - rgb : bool, default=False + rgb : bool, optional Whether `data` is an ``RGB`` or ``RGBA`` image. When ``rgb=True``, `data` is expected to have an extra dimension for the colour - channels (length ``3`` or ``4``). + channels (length ``3`` or ``4``). Defaults to ``False``. radius : float, optional The radius of the mesh sphere. Defaults to :data:`~geovista.common.RADIUS`. zlevel : int, default=0 @@ -1002,6 +1008,9 @@ def from_unstructured( .. versionadded:: 0.1.0 """ + if rgb is None: + rgb = False + xs, ys = np.asanyarray(xs), np.asanyarray(ys) shape = xs.shape diff --git a/src/geovista/cache/__init__.py b/src/geovista/cache/__init__.py index 85c8f08d..5caa5407 100644 --- a/src/geovista/cache/__init__.py +++ b/src/geovista/cache/__init__.py @@ -16,7 +16,7 @@ from functools import wraps import os from pathlib import Path -from typing import Any, AnyStr, IO, TYPE_CHECKING, TypeAlias +from typing import IO, TYPE_CHECKING, Any, AnyStr, TypeAlias import pooch @@ -94,7 +94,9 @@ @wraps(CACHE._fetch) # noqa: SLF001 -def _fetch(*args: str, **kwargs: bool | Callable[..., Any]) -> str: # numpydoc ignore=GL08 +def _fetch( + *args: str, **kwargs: bool | Callable[..., Any] +) -> str: # numpydoc ignore=GL08 # default to our http/s downloader with user-agent headers kwargs.setdefault("downloader", _downloader) result = CACHE._fetch(*args, **kwargs) # noqa: SLF001 @@ -150,16 +152,16 @@ def _downloader( return downloader(url, output_file, poocher, check_only=check_only) -def pooch_mute(silent: bool = True) -> bool: +def pooch_mute(silent: bool | None = None) -> bool: """Control the :mod:`pooch` cache manager logger verbosity. Updates the status variable :data:`GEOVISTA_POOCH_MUTE`. Parameters ---------- - silent : bool, default=True + silent : bool, optional Whether to silence or activate the :mod:`pooch` cache manager logger messages - to the console. + to the console. Defaults to ``True``. Returns ------- @@ -173,6 +175,9 @@ def pooch_mute(silent: bool = True) -> bool: """ global GEOVISTA_POOCH_MUTE # noqa: PLW0603 + if silent is None: + silent = True + level = "WARNING" if silent else "NOTSET" pooch.utils.get_logger().setLevel(level) original = GEOVISTA_POOCH_MUTE diff --git a/src/geovista/cli.py b/src/geovista/cli.py index 1fff9a74..52ee55e8 100644 --- a/src/geovista/cli.py +++ b/src/geovista/cli.py @@ -92,7 +92,9 @@ def _download_group( previous = pooch_mute(silent=True) - click.echo(f"Downloading {n_fnames} {name_post}registered asset{_plural(n_fnames)}:") + click.echo( + f"Downloading {n_fnames} {name_post}registered asset{_plural(n_fnames)}:" + ) for i, fname in enumerate(fnames): click.echo(f"[{i + 1:0{width}d}/{n_fnames}] Downloading ", nl=False) click.secho(f"{fname} ", nl=False, fg=fg_colour) @@ -101,8 +103,7 @@ def _download_group( name_path = pathlib.Path(fname) if decompress and (suffix := name_path.suffix) in pooch.Decompress.extensions: processor = pooch.Decompress( - method="auto", - name=name_path.stem.removesuffix(suffix) + method="auto", name=name_path.stem.removesuffix(suffix) ) CACHE.fetch(fname, processor=processor) click.secho("done!", fg="green") diff --git a/src/geovista/core.py b/src/geovista/core.py index 2cc40018..d0c7391a 100644 --- a/src/geovista/core.py +++ b/src/geovista/core.py @@ -45,8 +45,6 @@ from .search import find_cell_neighbours if TYPE_CHECKING: - from collections.abc import Iterable - from numpy.typing import ArrayLike import pyvista as pv diff --git a/src/geovista/crs.py b/src/geovista/crs.py index 2421dced..d13a0f81 100644 --- a/src/geovista/crs.py +++ b/src/geovista/crs.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import Any, TYPE_CHECKING, TypeAlias +from typing import TYPE_CHECKING, Any, TypeAlias import lazy_loader as lazy from pyproj import CRS diff --git a/src/geovista/geometry.py b/src/geovista/geometry.py index af8e522d..fdabc24a 100644 --- a/src/geovista/geometry.py +++ b/src/geovista/geometry.py @@ -143,7 +143,7 @@ def load_coastline_geometries( reader = shp.Reader(fname) def unpack( - geometries: Generator[LineString | MultiLineString] | list[GeometrySequence] + geometries: Generator[LineString | MultiLineString] | list[GeometrySequence], ) -> None: """Unpack the geometries coordinates. diff --git a/src/geovista/geoplotter.py b/src/geovista/geoplotter.py index 7e8a645a..eaa5bc57 100644 --- a/src/geovista/geoplotter.py +++ b/src/geovista/geoplotter.py @@ -15,7 +15,6 @@ from __future__ import annotations -from collections.abc import Callable, Iterable from functools import lru_cache import os from typing import TYPE_CHECKING, Any @@ -67,6 +66,8 @@ from .transform import transform_mesh, transform_point if TYPE_CHECKING: + from collections.abc import Callable + from numpy.typing import ArrayLike import pyvista as pv @@ -1312,7 +1313,8 @@ def view_poi( crs = self.crs # Assertion to appease MyPy. - assert x is not None and y is not None + assert x is not None + assert y is not None if crs != self.crs: x, y, _ = transform_point(crs, self.crs, x, y) diff --git a/src/geovista/gridlines.py b/src/geovista/gridlines.py index 6880530c..784ef2a5 100644 --- a/src/geovista/gridlines.py +++ b/src/geovista/gridlines.py @@ -13,7 +13,6 @@ from __future__ import annotations -from collections.abc import Iterable from dataclasses import dataclass from typing import TYPE_CHECKING diff --git a/src/geovista/pantry/textures.py b/src/geovista/pantry/textures.py index e1b84e79..8433e290 100644 --- a/src/geovista/pantry/textures.py +++ b/src/geovista/pantry/textures.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import TypeAlias, TYPE_CHECKING +from typing import TYPE_CHECKING, TypeAlias import lazy_loader as lazy From 59df3a951d54b7404e43b60ea333d039e3002666 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 29 Jan 2025 14:34:38 +0000 Subject: [PATCH 08/19] Test fixes. --- src/geovista/bridge.py | 7 +++++-- src/geovista/core.py | 2 +- src/geovista/geodesic.py | 4 ++-- src/geovista/gridlines.py | 11 +++++++++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/geovista/bridge.py b/src/geovista/bridge.py index 4df4de06..bc424337 100644 --- a/src/geovista/bridge.py +++ b/src/geovista/bridge.py @@ -719,7 +719,7 @@ def from_points( @classmethod def from_tiff( cls, - fname: Path, + fname: Path | str, name: str | None = None, band: int = 1, rgb: bool | None = None, @@ -738,7 +738,7 @@ def from_tiff( Parameters ---------- - fname : Path + fname : Path or str The file path to the GeoTIFF. name : str, optional The name of the GeoTIFF data array to be attached to the mesh. @@ -821,6 +821,9 @@ def from_tiff( if rgb is None: rgb = False + if isinstance(fname, str): + fname = Path(fname) + fname = fname.resolve(strict=True) band = -1 if rgb else band diff --git a/src/geovista/core.py b/src/geovista/core.py index d0c7391a..b804d2c1 100644 --- a/src/geovista/core.py +++ b/src/geovista/core.py @@ -547,7 +547,7 @@ def resize( ) else: new_radius = radius + radius * zlevel * zscale - update = new_radius is not None and not np.isclose(distance(mesh), new_radius) + update = bool(new_radius) and not np.isclose(distance(mesh), new_radius) if update: lonlat = from_cartesian(mesh) diff --git a/src/geovista/geodesic.py b/src/geovista/geodesic.py index 380dc331..8b85cb7a 100644 --- a/src/geovista/geodesic.py +++ b/src/geovista/geodesic.py @@ -403,8 +403,8 @@ def bbox_update( """ assert row is not None or column is not None - row_slice = slice(None) if row is None else slice(row, row + 1) - column_slice = slice(None) if column is None else slice(column, column + 1) + row_slice = np.s_[:] if row is None else np.s_[row] + column_slice = np.s_[:] if column is None else np.s_[column] glons, glats = npoints_by_idx( self._bbox_lons, diff --git a/src/geovista/gridlines.py b/src/geovista/gridlines.py index 784ef2a5..577cca05 100644 --- a/src/geovista/gridlines.py +++ b/src/geovista/gridlines.py @@ -13,6 +13,7 @@ from __future__ import annotations +from collections.abc import Iterable from dataclasses import dataclass from typing import TYPE_CHECKING @@ -169,7 +170,7 @@ def _step_period(lon: float, lat: float) -> tuple[float, float]: return (lon, lat) -def create_meridian_labels(lons: list[float]) -> list[str]: +def create_meridian_labels(lons: list[float] | float) -> list[str]: """Generate labels for the meridians. Parameters @@ -190,6 +191,9 @@ def create_meridian_labels(lons: list[float]) -> list[str]: """ result = [] + if not isinstance(lons, Iterable): + lons = [lons] + for lon in lons: # TODO @bjlittle: Explicit truncation is performed, perhaps offer format # control when required. @@ -365,7 +369,7 @@ def create_meridians( def create_parallel_labels( - lats: list[float], poles_parallel: bool | None = None + lats: list[float] | float, poles_parallel: bool | None = None ) -> list[str]: """Generate labels for the parallels. @@ -393,6 +397,9 @@ def create_parallel_labels( if poles_parallel is None: poles_parallel = LATITUDE_POLES_PARALLEL + if not isinstance(lats, Iterable): + lats = [lats] + for lat in lats: # explicit truncation, perhaps offer format control when required value = int(lat) From 1588cf3f6035c2938e8c97583a199aa29d6b5d5d Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 29 Jan 2025 14:51:24 +0000 Subject: [PATCH 09/19] Address third-party MyPy problems. --- pyproject.toml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c5814159..e9645ba8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,20 +59,23 @@ skip = ".git,*.css,*.ipynb,*.js,*.html,*.map,*.po,CODE_OF_CONDUCT.md" [tool.mypy] -strict = true -# TODO: fix pyvistaqt -disallow_subclassing_any = false -# TODO: fix click? -disallow_untyped_decorators = false enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] exclude = [] files = ["src/geovista"] +strict = true warn_unreachable = true -# -# -#[[tool.mypy.overrides]] -#module = "click" -#no_implicit_reexport = false + + +[[tool.mypy.overrides]] +# Problem caused by the click module - out of our control. +disallow_untyped_decorators = false +module = "geovista.cli" + + +[[tool.mypy.overrides]] +# Subclassing third-party classes - out of our control. +disallow_subclassing_any = false +module = "geovista.report,geovista.geoplotter,geovista.qt" [tool.numpydoc_validation] From 7b195ad51b9282154f3f3170b4e0a4aa7b715819 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 29 Jan 2025 14:55:49 +0000 Subject: [PATCH 10/19] Better handling of rgb parameter. --- src/geovista/bridge.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/geovista/bridge.py b/src/geovista/bridge.py index bc424337..f936a68e 100644 --- a/src/geovista/bridge.py +++ b/src/geovista/bridge.py @@ -409,7 +409,7 @@ def from_1d( data: ArrayLike | None = None, name: str | None = None, crs: CRSLike | None = None, - rgb: bool | None = None, + rgb: bool | None = False, radius: float | None = None, zlevel: int | None = None, zscale: float | None = None, @@ -449,10 +449,10 @@ def from_1d( The Coordinate Reference System of the provided `xs` and `ys`. May be anything accepted by :meth:`pyproj.crs.CRS.from_user_input`. Defaults to ``EPSG:4326`` i.e., ``WGS 84``. - rgb : bool, optional + rgb : bool, default=False Whether `data` is an ``RGB`` or ``RGBA`` image. When ``rgb=True``, `data` is expected to have an extra dimension for the colour - channels (length ``3`` or ``4``). Defaults to ``False``. + channels (length ``3`` or ``4``). radius : float, optional The radius of the sphere. Defaults to :data:`~geovista.common.RADIUS`. zlevel : int, default=0 @@ -503,7 +503,7 @@ def from_2d( data: ArrayLike | None = None, name: str | None = None, crs: CRSLike | None = None, - rgb: bool | None = None, + rgb: bool | None = False, radius: float | None = None, zlevel: int | None = None, zscale: float | None = None, @@ -544,10 +544,10 @@ def from_2d( The Coordinate Reference System of the provided `xs` and `ys`. May be anything accepted by :meth:`pyproj.crs.CRS.from_user_input`. Defaults to ``EPSG:4326`` i.e., ``WGS 84``. - rgb : bool, optional + rgb : bool, default=False Whether `data` is an ``RGB`` or ``RGBA`` image. When ``rgb=True``, `data` is expected to have an extra dimension for the colour - channels (length ``3`` or ``4``). Defaults to ``False``. + channels (length ``3`` or ``4``). radius : float, optional The radius of the sphere. Defaults to :data:`~geovista.common.RADIUS`. zlevel : int, default=0 @@ -722,7 +722,7 @@ def from_tiff( fname: Path | str, name: str | None = None, band: int = 1, - rgb: bool | None = None, + rgb: bool | None = False, sieve: bool | None = False, size: int | None = None, extract: bool | None = False, @@ -748,9 +748,9 @@ def from_tiff( band : int, default=1 The band index to read from the GeoTIFF. Note that, the `band` index is one-based. - rgb : bool, optional + rgb : bool, default=False Specify whether to read the GeoTIFF as an ``RGB`` or ``RGBA`` image. - When ``rgb=True``, the `band` index is ignored. Defaults to ``False``. + When ``rgb=True``, the `band` index is ignored. sieve : bool, default=False Specify whether to sieve the GeoTIFF mask to remove small connected regions. See :func:`rasterio.features.sieve` for more information. @@ -931,7 +931,7 @@ def from_unstructured( start_index: int | None = None, name: str | None = None, crs: CRSLike | None = None, - rgb: bool | None = None, + rgb: bool | None = False, radius: float | None = None, zlevel: int | None = None, zscale: float | None = None, @@ -983,10 +983,10 @@ def from_unstructured( The Coordinate Reference System of the provided `xs` and `ys`. May be anything accepted by :meth:`pyproj.crs.CRS.from_user_input`. Defaults to ``EPSG:4326`` i.e., ``WGS 84``. - rgb : bool, optional + rgb : bool, default=False Whether `data` is an ``RGB`` or ``RGBA`` image. When ``rgb=True``, `data` is expected to have an extra dimension for the colour - channels (length ``3`` or ``4``). Defaults to ``False``. + channels (length ``3`` or ``4``). radius : float, optional The radius of the mesh sphere. Defaults to :data:`~geovista.common.RADIUS`. zlevel : int, default=0 From 2832d9b8c0bc7b719445079895dca2d6c7a6cb3d Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 29 Jan 2025 17:14:26 +0000 Subject: [PATCH 11/19] Change log entry. --- changelog/1300.enhancement.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/1300.enhancement.rst diff --git a/changelog/1300.enhancement.rst b/changelog/1300.enhancement.rst new file mode 100644 index 00000000..f2d4245d --- /dev/null +++ b/changelog/1300.enhancement.rst @@ -0,0 +1,3 @@ +Made the entire code base MyPy-compliant. This should make programming faster +and more intuitive for anyone using an IDE and/or any sort of type-checking. +(:user:`trexfeathers`) From fca50dfc4de17d4c5ed309c16d5fda4780fd90d6 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 31 Jan 2025 11:47:17 +0000 Subject: [PATCH 12/19] Remove overzealous rgb None checking. --- src/geovista/bridge.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/geovista/bridge.py b/src/geovista/bridge.py index f936a68e..49767415 100644 --- a/src/geovista/bridge.py +++ b/src/geovista/bridge.py @@ -477,9 +477,6 @@ def from_1d( .. versionadded:: 0.1.0 """ - if rgb is None: - rgb = False - xs, ys = cls._as_contiguous_1d(xs, ys) mxs, mys = np.meshgrid(xs, ys, indexing="xy") return Transform.from_2d( @@ -818,9 +815,6 @@ def from_tiff( ) raise ImportError(emsg) from None - if rgb is None: - rgb = False - if isinstance(fname, str): fname = Path(fname) From b9c3708fbdf920717d33c1fee30e7e3ba11cf2a3 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 31 Jan 2025 11:49:14 +0000 Subject: [PATCH 13/19] Better changelog entry. --- changelog/1300.enhancement.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog/1300.enhancement.rst b/changelog/1300.enhancement.rst index f2d4245d..19bb9c6d 100644 --- a/changelog/1300.enhancement.rst +++ b/changelog/1300.enhancement.rst @@ -1,3 +1,3 @@ -Made the entire code base MyPy-compliant. This should make programming faster -and more intuitive for anyone using an IDE and/or any sort of type-checking. -(:user:`trexfeathers`) +Made the entire code base `mypy `__ compliant. +This should make development faster and more intuitive for anyone using an IDE +and/or any sort of type-checking. From c9e9b46240b9ff8b1a86122661bafbb264ad31d0 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 31 Jan 2025 11:52:01 +0000 Subject: [PATCH 14/19] Improve PathLike type alias. --- src/geovista/bridge.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/geovista/bridge.py b/src/geovista/bridge.py index 49767415..9eac919c 100644 --- a/src/geovista/bridge.py +++ b/src/geovista/bridge.py @@ -19,7 +19,7 @@ from __future__ import annotations -from pathlib import Path, PurePath +from pathlib import Path from typing import TYPE_CHECKING, TypeAlias import warnings @@ -61,7 +61,7 @@ ] # type aliases -PathLike: TypeAlias = str | PurePath +PathLike: TypeAlias = str | Path """Type alias for an asset file path.""" Shape: TypeAlias = tuple[int, ...] @@ -716,7 +716,7 @@ def from_points( @classmethod def from_tiff( cls, - fname: Path | str, + fname: PathLike, name: str | None = None, band: int = 1, rgb: bool | None = False, @@ -735,7 +735,7 @@ def from_tiff( Parameters ---------- - fname : Path or str + fname : PathLike The file path to the GeoTIFF. name : str, optional The name of the GeoTIFF data array to be attached to the mesh. From b172df70ecdccf95223b3cad53667731fedf051d Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 31 Jan 2025 11:55:10 +0000 Subject: [PATCH 15/19] Don't bother setting band to None if rgb. --- src/geovista/bridge.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/geovista/bridge.py b/src/geovista/bridge.py index 9eac919c..9f280e55 100644 --- a/src/geovista/bridge.py +++ b/src/geovista/bridge.py @@ -819,7 +819,6 @@ def from_tiff( fname = Path(fname) fname = fname.resolve(strict=True) - band = -1 if rgb else band if size is None: size = RIO_SIEVE_SIZE From 357c786ce7e141cccdaf8e7c340645db264d780c Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 31 Jan 2025 12:30:38 +0000 Subject: [PATCH 16/19] Remove unnecessary assertion. --- src/geovista/bridge.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/geovista/bridge.py b/src/geovista/bridge.py index 9f280e55..a623766e 100644 --- a/src/geovista/bridge.py +++ b/src/geovista/bridge.py @@ -1155,7 +1155,6 @@ def from_unstructured( if not name: size = data.size // data.shape[-1] if rgb else data.size name = NAME_POINTS if size == mesh.n_points else NAME_CELLS - assert isinstance(name, str) mesh.field_data[GV_FIELD_NAME] = np.array([name]) mesh[name] = data From 15afe63d88ae2ac973fdead4fba3850077fa7570 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 31 Jan 2025 13:38:34 +0000 Subject: [PATCH 17/19] Alter pyproject.toml format. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e9645ba8..98fba596 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,7 @@ module = "geovista.cli" [[tool.mypy.overrides]] # Subclassing third-party classes - out of our control. disallow_subclassing_any = false -module = "geovista.report,geovista.geoplotter,geovista.qt" +module = ["geovista.geoplotter", "geovista.qt", "geovista.report"] [tool.numpydoc_validation] From edb1fe1b4fd70020174f1d23628beabb5a88323e Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 31 Jan 2025 13:43:23 +0000 Subject: [PATCH 18/19] Nicer type narrowing. --- src/geovista/cache/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/geovista/cache/__init__.py b/src/geovista/cache/__init__.py index dc1ba34a..a5675ed6 100644 --- a/src/geovista/cache/__init__.py +++ b/src/geovista/cache/__init__.py @@ -99,8 +99,7 @@ def _fetch( ) -> str: # numpydoc ignore=GL08 # default to our http/s downloader with user-agent headers kwargs.setdefault("downloader", _downloader) - result = CACHE._fetch(*args, **kwargs) # noqa: SLF001 - assert isinstance(result, str) + result: str = CACHE._fetch(*args, **kwargs) # noqa: SLF001 return result From a52770a5f2fbd7a950f1f8ad6abce679ad486bf8 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Fri, 31 Jan 2025 13:48:54 +0000 Subject: [PATCH 19/19] TYPE_CHECKING Sequence import and correct use of BBOX_C in docstring parameters. --- src/geovista/geodesic.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/geovista/geodesic.py b/src/geovista/geodesic.py index 8b85cb7a..9b7909c4 100644 --- a/src/geovista/geodesic.py +++ b/src/geovista/geodesic.py @@ -13,7 +13,7 @@ from __future__ import annotations -from collections.abc import Iterable, Sequence +from collections.abc import Iterable from typing import TYPE_CHECKING, TypeAlias import warnings @@ -32,6 +32,8 @@ from .crs import WGS84, to_wkt if TYPE_CHECKING: + from collections.abc import Sequence + import numpy as np from numpy.typing import ArrayLike import pyproj @@ -168,8 +170,9 @@ def __init__( ellps : str, optional The ellipsoid for geodesic calculations. See :func:`pyproj.list.get_ellps_map`. Defaults to :data:`ELLIPSE`. - c : float, default=:data:`BBOX_C` + c : float, optional The bounding-box face geometry will contain ``c**2`` cells. + Defaults to :data:`BBOX_C`. triangulate : bool, optional Specify whether the bounding-box faces are triangulated. Defaults to ``False``. @@ -1085,8 +1088,9 @@ def panel( ellps : str, optional The ellipsoid for geodesic calculations. See :func:`pyproj.list.get_ellps_map`. Defaults to :data:`ELLIPSE`. - c : float, default=:data:`BBOX_C` - The bounding-box face geometry will contain ``c**2`` cells. + c : float, optional + The bounding-box face geometry will contain ``c**2`` cells. Defaults + to :data:`BBOX_C`. triangulate : bool, optional Specify whether the panel bounding-box faces are triangulated. Defaults to ``False``. @@ -1157,8 +1161,9 @@ def wedge( ellps : str, optional The ellipsoid for geodesic calculations. See :func:`pyproj.list.get_ellps_map`. Defaults to :data:`ELLIPSE`. - c : float, default=:data:`BBOX_C` - The bounding-box face geometry will contain ``c**2`` cells. + c : float, optional + The bounding-box face geometry will contain ``c**2`` cells. Defaults + to :data:`BBOX_C`. triangulate : bool, optional Specify whether the wedge bounding-box faces are triangulated. Defaults to ``False``.