From 3e79bf1f3f4afbe3c6dc8141c3d305fbb0bf31c3 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Wed, 22 Nov 2023 23:57:52 +0000 Subject: [PATCH] enforce all ruff rules (#531) --- .ruff.toml | 45 +++++++++++ MANIFEST.in | 1 + pyproject.toml | 56 ++++++++++---- setup.py | 0 src/geovista/bridge.py | 12 +-- src/geovista/cache.py | 4 +- src/geovista/cli.py | 6 +- src/geovista/common.py | 61 +++++++-------- src/geovista/core.py | 43 ++++++----- src/geovista/crs.py | 19 +++-- src/geovista/examples/clouds.py | 2 +- src/geovista/examples/clouds_robin.py | 2 +- src/geovista/examples/earthquakes.py | 2 +- src/geovista/examples/earthquakes_wink1.py | 2 +- src/geovista/examples/from_1d__oisst.py | 2 +- src/geovista/examples/from_1d__oisst_eqc.py | 2 +- .../examples/from_1d__synthetic_face_m1_n1.py | 2 +- .../from_1d__synthetic_face_m1_n1_robin.py | 2 +- .../examples/from_1d__synthetic_node_m1_n1.py | 2 +- .../from_1d__synthetic_node_m1_n1_moll.py | 2 +- src/geovista/examples/from_2d__orca.py | 2 +- src/geovista/examples/from_2d__orca_moll.py | 2 +- .../examples/from_2d__synthetic_face_m1n1.py | 2 +- .../from_2d__synthetic_face_m1n1_robin.py | 2 +- .../examples/from_2d__synthetic_node_m1n1.py | 2 +- .../from_2d__synthetic_node_m1n1_moll.py | 2 +- .../examples/from_points__orca_cloud.py | 3 +- .../examples/from_points__orca_cloud_eqc.py | 3 +- .../examples/from_unstructured__fesom.py | 2 +- .../examples/from_unstructured__fesom_fouc.py | 2 +- .../examples/from_unstructured__fvcom.py | 2 +- .../examples/from_unstructured__icon.py | 2 +- .../examples/from_unstructured__icon_eqc.py | 2 +- .../from_unstructured__icosahedral.py | 2 +- .../from_unstructured__icosahedral_poly.py | 2 +- .../from_unstructured__lam_pacific.py | 2 +- .../from_unstructured__lam_pacific_moll.py | 2 +- .../examples/from_unstructured__lfric_orog.py | 2 +- .../from_unstructured__lfric_orog_warp.py | 2 +- .../examples/from_unstructured__lfric_sst.py | 2 +- .../from_unstructured__lfric_sst_bonne.py | 2 +- .../examples/from_unstructured__smc.py | 2 +- .../examples/from_unstructured__smc_sinu.py | 2 +- .../examples/from_unstructured__tri.py | 2 +- .../examples/from_unstructured__tri_hammer.py | 2 +- src/geovista/examples/uber_h3.py | 17 ++--- src/geovista/examples/vectors.py | 2 +- src/geovista/geodesic.py | 67 ++++++++--------- src/geovista/geometry.py | 8 +- src/geovista/geoplotter.py | 34 ++++----- src/geovista/gridlines.py | 39 +++++----- src/geovista/pantry.py | 75 ++++++------------- src/geovista/report.py | 4 +- src/geovista/samples.py | 51 ++++--------- src/geovista/search.py | 24 +++--- src/geovista/transform.py | 23 +++--- tests/bridge/test__as_compatible_data.py | 2 +- tests/common/conftest.py | 4 +- tests/common/test_Preference.py | 2 +- tests/common/test_distance.py | 12 ++- tests/common/test_from_cartesian.py | 14 ++-- tests/common/test_nan_mask.py | 2 +- tests/common/test_sanitize_data.py | 6 +- tests/common/test_to_cartesian.py | 6 +- tests/common/test_to_lonlat.py | 2 +- tests/common/test_to_lonlats.py | 2 +- tests/common/test_wrap.py | 3 +- tests/conftest.py | 20 ++--- tests/core/test_slice_lines.py | 18 ++--- tests/crs/test_has_wkt.py | 2 +- tests/geodesic/conftest.py | 2 +- tests/geodesic/test_Preference.py | 2 +- tests/geodesic/test_line.py | 10 +-- tests/geoplotter/test_add_points.py | 4 +- tests/gridlines/test__step_period.py | 22 +++--- .../gridlines/test_create_meridian_labels.py | 34 ++++----- tests/gridlines/test_create_meridians.py | 4 +- .../gridlines/test_create_parallel_labels.py | 46 ++++++------ tests/gridlines/test_create_parallels.py | 4 +- tests/plotting/test_examples.py | 2 +- tests/search/conftest.py | 38 ++++++++-- tests/search/test_Preference.py | 2 +- tests/search/test_find_nearest_cell.py | 4 +- tests/transform/test_transform_points.py | 6 +- 84 files changed, 502 insertions(+), 436 deletions(-) create mode 100644 .ruff.toml mode change 100644 => 100755 setup.py diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 00000000..4e1446e5 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,45 @@ +extend = "pyproject.toml" + +lint.ignore = [ + # NOTE: To find a rule code to fix, run: + # ruff --select="ALL" --statistics src/geovista/ + + # flake8-boolean-trap (FBT) + # NOTE: A good thing to fix, but changes API. + "FBT001", # boolean-positional-arg-in-function-definition. + "FBT002", # boolean-default-value-in-function-definition. + "FBT003", # boolean-positional-value-in-function-call. + + # Pylint (PL) + "PLR0912", # Too many branches. + "PLR0913", # Too many arguments in function definition. + "PLR0915", # Too many statements. + "PLR2004", # Magic value used in comparison, consider replacing with a constant. + "PLW0603", # Using the global statement to update is discouraged. + + # flake8-self (SLF) + "SLF001", # Private member accessed. + + # flake8-todos (TD) + "TD003", # Missing issue link on the line following this TODO. +] + +[lint.extend-per-file-ignores] +"conftest.py" = [ + # flake8-annotations (ANN) + "ANN001", # Missing type annotation for function argument. + "ANN201", # Missing return type annotation for public function. +] +"src/geovista/geoplotter.py" = [ + # flake8-annotations (ANN) + "ANN401", # Dynamically typed expressions (typing.Any). +] +"test_*.py" = [ + # flake8-annotations (ANN) + "ANN001", # Missing type annotation for function argument. + "ANN201", # Missing return type annotation for public funciton. +] +"test_slice_lines.py" = [ + # eradicate (ERA) + "ERA001", # Found commented-out code. +] diff --git a/MANIFEST.in b/MANIFEST.in index 41c138c8..3096fe4b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -11,6 +11,7 @@ include .gitattributes exclude .gitignore exclude .pre-commit-config.yaml exclude .readthedocs.yml +exclude .ruff.toml include *.md include CITATION.cff exclude codecov.yml diff --git a/pyproject.toml b/pyproject.toml index e3d899c9..fbd043b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -115,17 +115,38 @@ target-version = "py310" [tool.ruff.lint] ignore = [ - "D105", # pydocstyle: undocumented-magic-method + # NOTE: Non-permanent exclusions should be added to ".ruff.toml". + + # flake8-annotations (ANN) + "ANN101", # Missing type annotation for 'self' in method. + "ANN102", # Missing type annotation for 'cls' in classmethod. + + # flake8-commas (COM) + "COM812", # Trailing comma missing. + "COM819", # Trailing comma prohibited. + + # pydocstyle (D) + "D105", # Missing docstring in magic method. + + # flake8-fixme (FIX) + "FIX002", # Line contains TODO, consider resolving the issue. + + # pep8-naming + "N806", # Variable name in function should be lowercase. + "N999", # Invalid module name. + + # pandas-vet (PD) + "PD", + + # Ruff-specific rules (RUF) + "RUF005", # Consider {expression} instead of concatenation. + + # flake8-bandit (S) + "S101", # Use of assert detected. ] +preview = false select = [ - "B", # flake8-bugbear - "C4", # flake8-comprehensions - "I", # isort - "E", # pycodestyle - "W", - "D", # pydocstyle - "F", # pyflakes - "UP", # pyupgrade + "ALL", ] @@ -136,13 +157,22 @@ required-imports = ["from __future__ import annotations"] [tool.ruff.lint.mccabe] -max-complexity = 22 +max-complexity = 26 [tool.ruff.lint.per-file-ignores] -"src/geovista/examples/earthquakes.py" = ["E501"] -"src/geovista/examples/earthquakes_wink1.py" = ["E501"] -"src/geovista/report.py" = ["E722"] +"src/geovista/cli.py" = [ + "T201", # flake8-print: print found. +] +"src/geovista/examples/*.py" = [ + "TCH002", # flake8-type-checking: Move 3rd-party import into a type checking block. +] +"src/geovista/examples/earthquakes.py" = [ + "E501", # pycodestyle: Line too long. +] +"src/geovista/examples/earthquakes_wink1.py" = [ + "E501", # pycodestyle: Line too long. +] [tool.ruff.lint.pydocstyle] diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 diff --git a/src/geovista/bridge.py b/src/geovista/bridge.py index ce107919..7fe8dd25 100644 --- a/src/geovista/bridge.py +++ b/src/geovista/bridge.py @@ -11,11 +11,11 @@ """ from __future__ import annotations +from typing import TYPE_CHECKING import warnings import numpy as np from numpy import ma -from numpy.typing import ArrayLike from pyproj import CRS import pyvista as pv @@ -32,6 +32,9 @@ from .crs import WGS84, CRSLike, to_wkt from .transform import transform_points +if TYPE_CHECKING: + from numpy.typing import ArrayLike + __all__ = ["Transform"] # type aliases @@ -217,9 +220,7 @@ def _create_connectivity_m1n1(shape: Shape) -> np.ndarray: nodes_c2 = np.ravel(idxs[:-1, 1:]).reshape(-1, 1) nodes_c3 = np.ravel(idxs[:-1, :-1]).reshape(-1, 1) - connectivity = np.hstack([nodes_c0, nodes_c1, nodes_c2, nodes_c3]) - - return connectivity + return np.hstack([nodes_c0, nodes_c1, nodes_c2, nodes_c3]) @staticmethod def _create_connectivity_mn4(shape: Shape) -> np.ndarray: @@ -258,9 +259,8 @@ def _create_connectivity_mn4(shape: Shape) -> np.ndarray: # we know that we can only be dealing with a quad mesh npts = np.prod(shape) * 4 - connectivity = np.arange(npts, dtype=np.uint32).reshape(-1, 4) - return connectivity + return np.arange(npts, dtype=np.uint32).reshape(-1, 4) @staticmethod def _verify_2d(xs: ArrayLike, ys: ArrayLike) -> None: diff --git a/src/geovista/cache.py b/src/geovista/cache.py index efccb8dd..b4df6fb8 100644 --- a/src/geovista/cache.py +++ b/src/geovista/cache.py @@ -183,8 +183,8 @@ def fetch_coastlines(resolution: str | None = None) -> pv.PolyData: fname = f"ne_coastlines_{resolution}.vtk" processor = pooch.Decompress(method="auto", name=fname) resource = CACHE.fetch(f"natural_earth/physical/{fname}.bz2", processor=processor) - mesh = pv.read(resource) - return mesh + + return pv.read(resource) def natural_earth_1(location: bool | None = False) -> TextureLike: diff --git a/src/geovista/cli.py b/src/geovista/cli.py index f45f7df5..175dcf22 100644 --- a/src/geovista/cli.py +++ b/src/geovista/cli.py @@ -265,7 +265,7 @@ def download( previous_path = CACHE.path CACHE.path = output - def collect(prefix): + def collect(prefix: str) -> list[str]: return list(filter(lambda item: item.startswith(prefix), fnames)) if pull: @@ -380,7 +380,7 @@ def collect(prefix): is_flag=True, help="Enable example diagnostics.", ) -def examples(run_all, show, run, verbose): +def examples(run_all: bool, show: bool, run: bool, verbose: bool) -> None: """Execute a geovista example script.""" # account for the initial "all" option n_scripts = len(SCRIPTS) - 1 @@ -436,7 +436,7 @@ def examples(run_all, show, run, verbose): is_flag=True, help="Add a base layer", ) -def plot(fname, axes, base) -> None: +def plot(fname: str, axes: bool, base: bool) -> None: """Load and render a VTK mesh.""" mesh = pv.read(fname) plotter = GeoPlotter() diff --git a/src/geovista/common.py b/src/geovista/common.py index ec3eb457..78efc22c 100644 --- a/src/geovista/common.py +++ b/src/geovista/common.py @@ -10,16 +10,18 @@ from collections.abc import Iterable from enum import Enum import sys -from typing import Any +from typing import TYPE_CHECKING import numpy as np from numpy import ma -from numpy.typing import ArrayLike import pyvista as pv from pyvista import _vtk from pyvista.core.filters import _get_output from vtk import vtkLogger, vtkObject +if TYPE_CHECKING: + from numpy.typing import ArrayLike + __all__ = [ "BASE", "CENTRAL_MERIDIAN", @@ -60,9 +62,7 @@ "wrap", ] -# -# TODO: support richer default management -# +# TODO @bjlittle: support richer default management #: Default base for wrapped longitude half-open interval, in degrees. BASE: float = -180.0 @@ -214,11 +214,11 @@ def __str__(self) -> str: .. versionadded:: 0.3.0 """ - # TODO: remove when minimum supported python version is 3.11 + # TODO @bjlittle: Remove when minimum supported python version is 3.11. return f"{self.name.lower()}" -# TODO: use StrEnum and auto when minimum supported python version is 3.11 +# TODO @bjlittle: Use StrEnum and auto when minimum supported python version is 3.11. class Preference(_MixinStrEnum, Enum): """Enumeration of common mesh geometry preferences. @@ -257,7 +257,7 @@ def active_kernel() -> bool: return result -def cast_UnstructuredGrid_to_PolyData( +def cast_UnstructuredGrid_to_PolyData( # noqa: N802 mesh: pv.UnstructuredGrid, clean: bool | None = False, ) -> pv.PolyData: @@ -405,22 +405,27 @@ def from_cartesian( zlevel = np.zeros_like(lons) - if cloud: - if GV_FIELD_RADIUS in mesh.field_data and GV_FIELD_ZSCALE in mesh.field_data: - # field data injected by geovista.bridge.Transform.from_points - base = mesh[GV_FIELD_RADIUS][0] - zscale = mesh[GV_FIELD_ZSCALE][0] - zlevel = (radius - base) / (base * zscale) + if ( + cloud + and GV_FIELD_RADIUS in mesh.field_data + and GV_FIELD_ZSCALE in mesh.field_data + ): + # field data injected by geovista.bridge.Transform.from_points + base = mesh[GV_FIELD_RADIUS][0] + zscale = mesh[GV_FIELD_ZSCALE][0] + zlevel = (radius - base) / (base * zscale) data = [lons, lats, zlevel] - # XXX: manage pole longitudes. an alternative future scheme could be more - # generic and inclusive, but this approach tackles the main use case for now - # TODO: refactor this into a separate function + # TODO @bjlittle: Manage pole longitudes. an alternative future scheme could be + # more generic and inclusive, but this approach tackles the main + # use case for now. + + # TODO @bjlittle: Refactor this into a separate function. pole_pids = np.where(np.isclose(np.abs(lats), 90))[0] if pole_pids.size: # enforce a common longitude for pole singularities - # TODO: review this strategy + # TODO @bjlittle: Review this strategy. lons[pole_pids] = 0 if ( @@ -437,7 +442,7 @@ def from_cartesian( pole_cids = np.unique(pole_submesh["vtkOriginalCellIds"]) for cid in pole_cids: # get the pids (point-indices) of the polar cell points - # XXX: pyvista 0.38.0: cell_point_ids(cid) -> get_cell(cid).point_ids + # NOTE: pyvista 0.38.0: cell_point_ids(cid) -> get_cell(cid).point_ids cell_pids = np.array(mesh.get_cell(cid).point_ids) # unfold polar quad-cells if len(cell_pids) == 4: @@ -478,7 +483,7 @@ def from_cartesian( seam_mask = np.isclose(np.abs(seam_lons), 180) lons[seam_ids[seam_mask]] = 180 elif mesh.n_lines: - # TODO: unify closed interval strategies for lines and cells + # TODO @bjlittle: Unify closed interval strategies for lines and cells. poi_mask = np.isclose(np.abs(lons), 180) if np.any(poi_mask): @@ -498,9 +503,7 @@ def from_cartesian( lons[pids] = 180 - result = np.vstack(data).T if stacked else np.array(data) - - return result + return np.vstack(data).T if stacked else np.array(data) def nan_mask(data: ArrayLike) -> np.ndarray: @@ -555,7 +558,7 @@ def point_cloud(mesh: pv.PolyData) -> bool: def sanitize_data( - *meshes: Any, + *meshes: Iterable[pv.PolyData], ) -> None: """Purge standard VTK helper cell and point data index arrays. @@ -691,9 +694,8 @@ def to_cartesian( y = np.ravel(radius * np.sin(y_rad) * np.sin(x_rad)) z = np.ravel(radius * np.cos(y_rad)) xyz = [x, y, z] - xyz = np.vstack(xyz).T if stacked else np.array(xyz) - return xyz + return np.vstack(xyz).T if stacked else np.array(xyz) def to_lonlat( @@ -817,8 +819,8 @@ def to_lonlats( lons = wrap(lons, base=base, period=period, rtol=rtol, atol=atol) z_radius = points[:, 2] / radius - # XXX: defensive clobber of values outside arcsin domain [-1, 1] - # which is the result of floating point inaccuracies at the extremes + # NOTE: defensive clobber of values outside arcsin domain [-1, 1] + # which is the result of floating point inaccuracies at the extremes if indices := np.where(z_radius > 1): z_radius[indices] = 1.0 if indices := np.where(z_radius < -1): @@ -828,9 +830,8 @@ def to_lonlats( lats = np.degrees(lats) data = [lons, lats] - result = np.vstack(data).T if stacked else np.array(data) - return result + return np.vstack(data).T if stacked else np.array(data) def triangulated(surface: pv.PolyData) -> bool: diff --git a/src/geovista/core.py b/src/geovista/core.py index e4c5033d..f8f7e3f8 100644 --- a/src/geovista/core.py +++ b/src/geovista/core.py @@ -9,11 +9,10 @@ import copy from enum import Enum, auto, unique -from typing import Any +from typing import TYPE_CHECKING import warnings import numpy as np -from numpy.typing import ArrayLike import pyvista as pv from .common import ( @@ -39,6 +38,11 @@ from .filters import remesh from .search import find_cell_neighbours +if TYPE_CHECKING: + from collections.abc import Iterable + + from numpy.typing import ArrayLike + __all__ = [ "MeridianSlice", "add_texture_coords", @@ -176,9 +180,8 @@ def _intersection( # the higher the number of spline interpolation "n_points" # the more accurate, but the more compute intensive and less performant spline = pv.Spline(xyz.points, n_points=n_points) - mesh = self.mesh.slice_along_line(spline) - return mesh + return self.mesh.slice_along_line(spline) def extract( self, @@ -311,7 +314,7 @@ def add_texture_coords( def combine( - *meshes: Any, + *meshes: Iterable[pv.PolyData], data: bool | None = True, clean: bool | None = False, ) -> pv.PolyData: @@ -421,7 +424,7 @@ def combine( faces = np.hstack(combined_faces) combined = pv.PolyData(points, faces=faces, n_faces=n_faces) - def combine_data(names, field=False): + def combine_data(names: set[str], field: bool | None = False) -> None: for name in names: if field: combined.field_data[name] = first[name] @@ -634,8 +637,9 @@ def slice_cells( if GV_REMESH_POINT_IDS not in result.point_data: result.point_data[GV_REMESH_POINT_IDS] = result[GV_POINT_IDS].copy() - # XXX: defensive removal of cells that span the meridian and should - # have been remeshed, but haven't due to their geometry ? + # TODO @bjlittle: Investigate defensive removal of cells that span the meridian + # and should have been remeshed, but haven't due to their + # geometry ? cids = set(find_cell_neighbours(result, remeshed[GV_CELL_IDS])) cids = cids.difference(set(remeshed_ids)) if cids: @@ -644,7 +648,7 @@ def slice_cells( neighbours.points = xy0 xdelta = [] for cid in range(neighbours.n_cells): - # XXX: pyvista 0.38.0: cell_points(cid) -> get_cell(cid).points + # NOTE: pyvista 0.38.0: cell_points(cid) -> get_cell(cid).points cxpts = neighbours.get_cell(cid).points[:, 0] cxmin, cxmax = cxpts.min(), cxpts.max() xdelta.append(cxmax - cxmin) @@ -664,7 +668,7 @@ def slice_cells( if meshes: result.remove_cells(np.unique(remeshed_ids), inplace=True) # reinstate field data purged by remove_cells - for field in mesh.field_data.keys(): + for field in mesh.field_data: result.field_data[field] = copy.deepcopy(mesh.field_data[field]) result = combine(result, *meshes) @@ -801,18 +805,18 @@ def slice_lines( # points and M cells i.e., # cid cid cid(new) # pid(0) --- pid(1) => pid(0) --- pid(new0) & pid(new1) -------- pid(1) - # [step1] [step2] + # # where, pid(new0) and pid(new1) are distinct pids but reference identical, but # not the same cartesian xyz point # - # [step1] + # n_points = points.shape[0] split_points = np.vstack(split_xyz) points = np.vstack([points, split_points]) # append M points new_pids = np.arange(n_points, points.shape[0]) split_lines = lines[split_cids] lines[split_cids, 2] = new_pids # inplace M cells - # [step2] + # n_points = points.shape[0] points = np.vstack([points, split_points]) # append M points new_pids = np.arange(n_points, points.shape[0]) @@ -824,7 +828,7 @@ def slice_lines( # points i.e., # cid0 cid1 cid0 cid1 # pid(0) ---- pid(1) ---- pid(2) => pid(0) ---- pid(new) & pid(1) ---- pid(2) - # [nop] + # # where, pid(new) and pid(1) are distinct pids but reference identical, but not # the same cartesian xyz point # @@ -842,7 +846,7 @@ def slice_lines( lines[detach_cids, 1:] = line_pids # inplace M cells if mesh.field_data.keys(): - for key in mesh.field_data.keys(): + for key in mesh.field_data: result.field_data[key] = mesh.field_data[key].copy() # TDB: given mesh.points, perform linear interpolation for new intersection points @@ -892,10 +896,9 @@ def slice_mesh( emsg = "Cannot slice a mesh that has been projected." raise ValueError(emsg) - result = ( - slice_lines(mesh, copy=True) - if mesh.n_lines - else slice_cells(mesh, antimeridian=True, rtol=rtol, atol=atol) - ) + if mesh.n_lines: + result = slice_lines(mesh, copy=True) + else: + result = slice_cells(mesh, antimeridian=True, rtol=rtol, atol=atol) return result diff --git a/src/geovista/crs.py b/src/geovista/crs.py index ac55f24b..bfedf9fe 100644 --- a/src/geovista/crs.py +++ b/src/geovista/crs.py @@ -7,12 +7,16 @@ """ from __future__ import annotations +from typing import TYPE_CHECKING + import numpy as np from pyproj import CRS -import pyvista as pv from .common import GV_FIELD_CRS +if TYPE_CHECKING: + import pyvista as pv + __all__ = [ "CRSLike", "PlateCarree", @@ -184,12 +188,13 @@ def set_central_meridian(crs: CRS, meridian: float) -> CRS | None: result = None crs_json = crs.to_json_dict() found = False - if conversion := crs_json.get("conversion"): - if parameters := conversion.get("parameters"): - for param in parameters: - if found := param["id"]["code"] == int(EPSG_CENTRAL_MERIDIAN): - param["value"] = meridian - break + if (conversion := crs_json.get("conversion")) and ( + parameters := conversion.get("parameters") + ): + for param in parameters: + if found := param["id"]["code"] == int(EPSG_CENTRAL_MERIDIAN): + param["value"] = meridian + break if found: result = CRS.from_json_dict(crs_json) diff --git a/src/geovista/examples/clouds.py b/src/geovista/examples/clouds.py index 345a3457..4c6b15d0 100755 --- a/src/geovista/examples/clouds.py +++ b/src/geovista/examples/clouds.py @@ -13,7 +13,7 @@ import geovista as gv from geovista.pantry import cloud_amount -import geovista.theme # noqa: F401 +import geovista.theme #: The colormap to render the clouds. CMAP = cmocean.cm.gray diff --git a/src/geovista/examples/clouds_robin.py b/src/geovista/examples/clouds_robin.py index 8686db08..735cc88d 100755 --- a/src/geovista/examples/clouds_robin.py +++ b/src/geovista/examples/clouds_robin.py @@ -13,7 +13,7 @@ import geovista as gv from geovista.pantry import cloud_amount -import geovista.theme # noqa: F401 +import geovista.theme #: The colormap to render the clouds. CMAP = cmocean.cm.gray diff --git a/src/geovista/examples/earthquakes.py b/src/geovista/examples/earthquakes.py index 3df29e36..4f198cff 100755 --- a/src/geovista/examples/earthquakes.py +++ b/src/geovista/examples/earthquakes.py @@ -12,7 +12,7 @@ import geovista as gv from geovista.pantry import usgs_earthquakes -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/earthquakes_wink1.py b/src/geovista/examples/earthquakes_wink1.py index ae065883..594ce457 100755 --- a/src/geovista/examples/earthquakes_wink1.py +++ b/src/geovista/examples/earthquakes_wink1.py @@ -12,7 +12,7 @@ import geovista as gv from geovista.pantry import usgs_earthquakes -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_1d__oisst.py b/src/geovista/examples/from_1d__oisst.py index 3aa16129..1b70736b 100755 --- a/src/geovista/examples/from_1d__oisst.py +++ b/src/geovista/examples/from_1d__oisst.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import oisst_avhrr_sst -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_1d__oisst_eqc.py b/src/geovista/examples/from_1d__oisst_eqc.py index 7e5d50b9..c02fe56c 100755 --- a/src/geovista/examples/from_1d__oisst_eqc.py +++ b/src/geovista/examples/from_1d__oisst_eqc.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import oisst_avhrr_sst -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_1d__synthetic_face_m1_n1.py b/src/geovista/examples/from_1d__synthetic_face_m1_n1.py index c571ebf0..c5a4ef68 100755 --- a/src/geovista/examples/from_1d__synthetic_face_m1_n1.py +++ b/src/geovista/examples/from_1d__synthetic_face_m1_n1.py @@ -11,7 +11,7 @@ import numpy as np import geovista as gv -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_1d__synthetic_face_m1_n1_robin.py b/src/geovista/examples/from_1d__synthetic_face_m1_n1_robin.py index 1dba41aa..aeb9df1b 100755 --- a/src/geovista/examples/from_1d__synthetic_face_m1_n1_robin.py +++ b/src/geovista/examples/from_1d__synthetic_face_m1_n1_robin.py @@ -11,7 +11,7 @@ import numpy as np import geovista as gv -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_1d__synthetic_node_m1_n1.py b/src/geovista/examples/from_1d__synthetic_node_m1_n1.py index a654a8c3..a5b1aec2 100755 --- a/src/geovista/examples/from_1d__synthetic_node_m1_n1.py +++ b/src/geovista/examples/from_1d__synthetic_node_m1_n1.py @@ -11,7 +11,7 @@ import numpy as np import geovista as gv -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_1d__synthetic_node_m1_n1_moll.py b/src/geovista/examples/from_1d__synthetic_node_m1_n1_moll.py index ab25a493..4e42b36d 100755 --- a/src/geovista/examples/from_1d__synthetic_node_m1_n1_moll.py +++ b/src/geovista/examples/from_1d__synthetic_node_m1_n1_moll.py @@ -11,7 +11,7 @@ import numpy as np import geovista as gv -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_2d__orca.py b/src/geovista/examples/from_2d__orca.py index 55656df4..cbddce70 100755 --- a/src/geovista/examples/from_2d__orca.py +++ b/src/geovista/examples/from_2d__orca.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import um_orca2 -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_2d__orca_moll.py b/src/geovista/examples/from_2d__orca_moll.py index 61e88acf..136eb268 100755 --- a/src/geovista/examples/from_2d__orca_moll.py +++ b/src/geovista/examples/from_2d__orca_moll.py @@ -13,7 +13,7 @@ import geovista as gv from geovista.common import cast_UnstructuredGrid_to_PolyData as cast from geovista.pantry import um_orca2 -import geovista.theme # noqa: F401 +import geovista.theme from geovista.transform import transform_mesh diff --git a/src/geovista/examples/from_2d__synthetic_face_m1n1.py b/src/geovista/examples/from_2d__synthetic_face_m1n1.py index 4cfe4e09..f61857ed 100755 --- a/src/geovista/examples/from_2d__synthetic_face_m1n1.py +++ b/src/geovista/examples/from_2d__synthetic_face_m1n1.py @@ -11,7 +11,7 @@ import numpy as np import geovista as gv -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_2d__synthetic_face_m1n1_robin.py b/src/geovista/examples/from_2d__synthetic_face_m1n1_robin.py index 755313b2..d1798ad6 100755 --- a/src/geovista/examples/from_2d__synthetic_face_m1n1_robin.py +++ b/src/geovista/examples/from_2d__synthetic_face_m1n1_robin.py @@ -11,7 +11,7 @@ import numpy as np import geovista as gv -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_2d__synthetic_node_m1n1.py b/src/geovista/examples/from_2d__synthetic_node_m1n1.py index 5fbd6b70..a6722934 100755 --- a/src/geovista/examples/from_2d__synthetic_node_m1n1.py +++ b/src/geovista/examples/from_2d__synthetic_node_m1n1.py @@ -11,7 +11,7 @@ import numpy as np import geovista as gv -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_2d__synthetic_node_m1n1_moll.py b/src/geovista/examples/from_2d__synthetic_node_m1n1_moll.py index 0a7680c3..5504f79e 100755 --- a/src/geovista/examples/from_2d__synthetic_node_m1n1_moll.py +++ b/src/geovista/examples/from_2d__synthetic_node_m1n1_moll.py @@ -11,7 +11,7 @@ import numpy as np import geovista as gv -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_points__orca_cloud.py b/src/geovista/examples/from_points__orca_cloud.py index 16270846..72cbde3b 100755 --- a/src/geovista/examples/from_points__orca_cloud.py +++ b/src/geovista/examples/from_points__orca_cloud.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """Importable and runnable geovista example. Notes @@ -10,7 +11,7 @@ import geovista as gv from geovista.pantry import um_orca2_gradient from geovista.samples import ZLEVEL_SCALE_CLOUD -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_points__orca_cloud_eqc.py b/src/geovista/examples/from_points__orca_cloud_eqc.py index 111ecb61..b540299b 100755 --- a/src/geovista/examples/from_points__orca_cloud_eqc.py +++ b/src/geovista/examples/from_points__orca_cloud_eqc.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """Importable and runnable geovista example. Notes @@ -10,7 +11,7 @@ import geovista as gv from geovista.pantry import um_orca2_gradient from geovista.samples import ZLEVEL_SCALE_CLOUD -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__fesom.py b/src/geovista/examples/from_unstructured__fesom.py index 95c0a85e..6d644ec4 100755 --- a/src/geovista/examples/from_unstructured__fesom.py +++ b/src/geovista/examples/from_unstructured__fesom.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import fesom -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__fesom_fouc.py b/src/geovista/examples/from_unstructured__fesom_fouc.py index c14dcb13..92d92bd9 100755 --- a/src/geovista/examples/from_unstructured__fesom_fouc.py +++ b/src/geovista/examples/from_unstructured__fesom_fouc.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import fesom -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__fvcom.py b/src/geovista/examples/from_unstructured__fvcom.py index 8588bfb3..9a51da61 100755 --- a/src/geovista/examples/from_unstructured__fvcom.py +++ b/src/geovista/examples/from_unstructured__fvcom.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import fvcom_tamar -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__icon.py b/src/geovista/examples/from_unstructured__icon.py index 76fd662f..92c48e1e 100755 --- a/src/geovista/examples/from_unstructured__icon.py +++ b/src/geovista/examples/from_unstructured__icon.py @@ -12,7 +12,7 @@ import geovista as gv from geovista.pantry import icon_soil -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__icon_eqc.py b/src/geovista/examples/from_unstructured__icon_eqc.py index 3eac77d1..911c05ba 100755 --- a/src/geovista/examples/from_unstructured__icon_eqc.py +++ b/src/geovista/examples/from_unstructured__icon_eqc.py @@ -12,7 +12,7 @@ import geovista as gv from geovista.pantry import icon_soil -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__icosahedral.py b/src/geovista/examples/from_unstructured__icosahedral.py index 7ec89b56..1988b6af 100755 --- a/src/geovista/examples/from_unstructured__icosahedral.py +++ b/src/geovista/examples/from_unstructured__icosahedral.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import icosahedral -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__icosahedral_poly.py b/src/geovista/examples/from_unstructured__icosahedral_poly.py index 1e3f33ec..e9a1268d 100755 --- a/src/geovista/examples/from_unstructured__icosahedral_poly.py +++ b/src/geovista/examples/from_unstructured__icosahedral_poly.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import icosahedral -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__lam_pacific.py b/src/geovista/examples/from_unstructured__lam_pacific.py index 1a13582b..1917798c 100755 --- a/src/geovista/examples/from_unstructured__lam_pacific.py +++ b/src/geovista/examples/from_unstructured__lam_pacific.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import lam_pacific -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__lam_pacific_moll.py b/src/geovista/examples/from_unstructured__lam_pacific_moll.py index 3a9cfd7e..b59e2548 100755 --- a/src/geovista/examples/from_unstructured__lam_pacific_moll.py +++ b/src/geovista/examples/from_unstructured__lam_pacific_moll.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import lam_pacific -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__lfric_orog.py b/src/geovista/examples/from_unstructured__lfric_orog.py index 2d3341ff..2a1804b9 100755 --- a/src/geovista/examples/from_unstructured__lfric_orog.py +++ b/src/geovista/examples/from_unstructured__lfric_orog.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import lfric_orog -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__lfric_orog_warp.py b/src/geovista/examples/from_unstructured__lfric_orog_warp.py index a4a9264a..3fdb29ca 100755 --- a/src/geovista/examples/from_unstructured__lfric_orog_warp.py +++ b/src/geovista/examples/from_unstructured__lfric_orog_warp.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import lfric_orog -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__lfric_sst.py b/src/geovista/examples/from_unstructured__lfric_sst.py index 71a2abea..ff7fde46 100755 --- a/src/geovista/examples/from_unstructured__lfric_sst.py +++ b/src/geovista/examples/from_unstructured__lfric_sst.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import lfric_sst -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__lfric_sst_bonne.py b/src/geovista/examples/from_unstructured__lfric_sst_bonne.py index a7da21f6..f90c64b6 100755 --- a/src/geovista/examples/from_unstructured__lfric_sst_bonne.py +++ b/src/geovista/examples/from_unstructured__lfric_sst_bonne.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import lfric_sst -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__smc.py b/src/geovista/examples/from_unstructured__smc.py index 1676358a..078a4bbf 100755 --- a/src/geovista/examples/from_unstructured__smc.py +++ b/src/geovista/examples/from_unstructured__smc.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import ww3_global_smc -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__smc_sinu.py b/src/geovista/examples/from_unstructured__smc_sinu.py index a87fdca8..f0fdac0f 100755 --- a/src/geovista/examples/from_unstructured__smc_sinu.py +++ b/src/geovista/examples/from_unstructured__smc_sinu.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import ww3_global_smc -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__tri.py b/src/geovista/examples/from_unstructured__tri.py index 052b7ab1..fa900b45 100755 --- a/src/geovista/examples/from_unstructured__tri.py +++ b/src/geovista/examples/from_unstructured__tri.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import ww3_global_tri -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/from_unstructured__tri_hammer.py b/src/geovista/examples/from_unstructured__tri_hammer.py index d0ebaa2f..f4d50856 100755 --- a/src/geovista/examples/from_unstructured__tri_hammer.py +++ b/src/geovista/examples/from_unstructured__tri_hammer.py @@ -10,7 +10,7 @@ import geovista as gv from geovista.pantry import ww3_global_tri -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/examples/uber_h3.py b/src/geovista/examples/uber_h3.py index 5d6b4c0f..1911e694 100755 --- a/src/geovista/examples/uber_h3.py +++ b/src/geovista/examples/uber_h3.py @@ -25,7 +25,7 @@ import numpy as np from numpy import ma from numpy.typing import ArrayLike -from pyvista import PolyData +from pyvista import Actor, PolyData import geovista from geovista.geodesic import line @@ -98,9 +98,8 @@ def to_mesh(h3indexes: H3Indexes) -> PolyData: # Create the mesh from the H3 geometry and topology. connectivity = ma.masked_equal(connectivity, MDI) - mesh = geovista.Transform.from_unstructured(lons, lats, connectivity=connectivity) - return mesh + return geovista.Transform.from_unstructured(lons, lats, connectivity=connectivity) def to_children(h3indexes: H3Indexes) -> H3Indexes: @@ -175,9 +174,7 @@ def generate_icosahedron_surface(resolution: int | None = 0) -> GeoSurface: [11, 7, 9], ] - surface = GeoSurface(lons=lons, lats=lats, connectivity=connectivity) - - return surface + return GeoSurface(lons=lons, lats=lats, connectivity=connectivity) def generate_geodesic_edges(surface: GeoSurface) -> PolyData: @@ -208,9 +205,7 @@ def generate_geodesic_edges(surface: GeoSurface) -> PolyData: meshes.append(line(lons, lats)) # Combine the geodesic lines into one mesh. - combined = meshes[0].append_polydata(*meshes[1:]) - - return combined + return meshes[0].append_polydata(*meshes[1:]) def add_checkboxes( @@ -218,7 +213,7 @@ def add_checkboxes( ) -> None: """Render the checkbox for each ``H3Asset``. - A checkbox is created for each actor will allows the visibility + A checkbox is created for each actor that allows the visibility of the actor to be toggled on/off within the `plotter` scene. Parameters @@ -236,7 +231,7 @@ def add_checkboxes( x, y = 10, 10 offset = size * 0.2 - def callback(actor, flag): + def callback(actor: Actor, flag: bool) -> None: actor.SetVisibility(flag) for i, slot in enumerate(sorted(H3Asset.__slots__)): diff --git a/src/geovista/examples/vectors.py b/src/geovista/examples/vectors.py index d57db9b5..5703d7aa 100755 --- a/src/geovista/examples/vectors.py +++ b/src/geovista/examples/vectors.py @@ -12,7 +12,7 @@ import geovista as gv from geovista.samples import regular_grid -import geovista.theme # noqa: F401 +import geovista.theme def main() -> None: diff --git a/src/geovista/geodesic.py b/src/geovista/geodesic.py index 5f2bf306..32935365 100644 --- a/src/geovista/geodesic.py +++ b/src/geovista/geodesic.py @@ -9,10 +9,10 @@ from collections.abc import Iterable from enum import Enum +from typing import TYPE_CHECKING import warnings import numpy as np -from numpy.typing import ArrayLike import pyproj import pyvista as pv @@ -28,6 +28,9 @@ from .common import cast_UnstructuredGrid_to_PolyData as cast from .crs import WGS84, to_wkt +if TYPE_CHECKING: + from numpy.typing import ArrayLike + __all__ = [ "BBox", "GEODESIC_NPTS", @@ -97,7 +100,7 @@ PREFERENCE: str = "center" -# TODO: use StrEnum and auto when minimum supported python version is 3.11 +# TODO @bjlittle: Use StrEnum and auto when minimum supported python version is 3.11. class Preference(_MixinStrEnum, Enum): """Enumeration of geodesic mesh geometry preferences. @@ -207,18 +210,19 @@ def __init__( # cache prior surface radius, as an optimisation self._surface_radius = None - def __eq__(self, other) -> bool: + def __eq__(self, other: BBox) -> bool: result = NotImplemented if isinstance(other, BBox): result = False lhs = (self.ellps, self.c, self.triangulate) rhs = (other.ellps, other.c, other.triangulate) - if all(x[0] == x[1] for x in zip(lhs, rhs, strict=True)): - if np.allclose(self.lons, other.lons): - result = np.allclose(self.lats, other.lats) + if all(x[0] == x[1] for x in zip(lhs, rhs, strict=True)) and np.allclose( + self.lons, other.lons + ): + result = np.allclose(self.lats, other.lats) return result - def __ne__(self, other) -> bool: + def __ne__(self, other: BBox) -> bool: result = self == other if result is not NotImplemented: result = not result @@ -229,11 +233,11 @@ def __repr__(self) -> str: f"ellps={self.ellps}, c={self.c}, n_points={self.mesh.n_points}, " f"n_cells={self.mesh.n_cells}" ) - result = f"{__package__}.{self.__class__.__name__}<{params}>" - return result + + return f"{__package__}.{self.__class__.__name__}<{params}>" @property - def mesh(self): + def mesh(self) -> pv.PolyData: """The bounding-box :class:`pyvista.PolyData` mesh.""" if self._mesh is None: self._generate_bbox_mesh() @@ -277,7 +281,7 @@ def _bbox_face_edge_idxs(self) -> np.ndarray: .. versionadded:: 0.1.0 """ - edge = np.concatenate( + return np.concatenate( [ self._idx_map[0], self._idx_map[1:, -1], @@ -285,7 +289,6 @@ def _bbox_face_edge_idxs(self) -> np.ndarray: self._idx_map[-2:0:-1, 0], ] ) - return edge def _generate_bbox_face(self) -> None: """Construct 2-D geodetic bounding-box surface defined by corners. @@ -313,7 +316,9 @@ def bbox_extend(lons: tuple[float], lats: tuple[float]) -> None: self._bbox_lats.extend(lats) self._bbox_count += len(lons) - def bbox_update(idx1, idx2, row=None, column=None) -> None: + def bbox_update( + idx1: int, idx2: int, row: int | None = None, column: int | None = None + ) -> None: assert row is not None or column is not None if row is None: row = slice(None) @@ -453,9 +458,7 @@ def _generate_bbox_skirt(self) -> np.ndarray: faces_c3 = faces_c2 + self._n_points faces_c4 = np.roll(faces_c3, 1) - faces = np.hstack([faces_n, faces_c1, faces_c2, faces_c3, faces_c4]) - - return faces + return np.hstack([faces_n, faces_c1, faces_c2, faces_c3, faces_c4]) def boundary( self, surface: pv.PolyData | None = None, radius: float | None = None @@ -577,9 +580,7 @@ def enclosed( selected[scalars].view(bool), adjacent_cells=False ) - region = cast(region) - - return region + return cast(region) def line( @@ -664,12 +665,11 @@ def line( lons, lats = np.asanyarray(lons), np.asanyarray(lats) # check whether to auto-repeat lons or lats - if lons.size == 1 or lats.size == 1: - if lons.size + lats.size > 2: - if lons.size == 1: - lons = np.repeat(lons[0], lats.size) - else: - lats = np.repeat(lats[0], lons.size) + if (lons.size == 1 or lats.size == 1) and (lons.size + lats.size > 2): + if lons.size == 1: + lons = np.repeat(lons[0], lats.size) + else: + lats = np.repeat(lats[0], lons.size) n_lons, n_lats = lons.size, lats.size @@ -690,13 +690,12 @@ def line( lons = wrap(lons) # check for minimal loop corner case - if np.isclose(lons[0], lons[-1]) and np.isclose(lats[0], lats[-1]): - if n_lons == 2: - emsg = ( - "Require a closed line (loop) geometry containing at least 3 " - f"longitude/latitude values, got '{n_lons}'." - ) - raise ValueError(emsg) + if np.isclose(lons[0], lons[-1]) and np.isclose(lats[0], lats[-1]) and n_lons == 2: + emsg = ( + "Require a closed line (loop) geometry containing at least 3 " + f"longitude/latitude values, got '{n_lons}'." + ) + raise ValueError(emsg) line_lons, line_lats = [], [] geod = pyproj.Geod(ellps=ellps) @@ -866,7 +865,7 @@ def npoints_by_idx( start_lonlat = lons[start_idx], lats[start_idx] end_lonlat = lons[end_idx], lats[end_idx] - result = npoints( + return npoints( *start_lonlat, *end_lonlat, npts=npts, @@ -876,8 +875,6 @@ def npoints_by_idx( geod=geod, ) - return result - def panel( name: int | str, diff --git a/src/geovista/geometry.py b/src/geovista/geometry.py index 8beebac9..148097e5 100644 --- a/src/geovista/geometry.py +++ b/src/geovista/geometry.py @@ -7,12 +7,13 @@ """ from __future__ import annotations +from collections.abc import Generator from functools import lru_cache import cartopy.io.shapereader as shp import numpy as np import pyvista as pv -from shapely.geometry.multilinestring import MultiLineString +from shapely import LineString, MultiLineString from .cache import fetch_coastlines from .common import ( @@ -33,6 +34,9 @@ "load_coastlines", ] +# type aliases +Geometries = Generator[LineString | MultiLineString] + @lru_cache(maxsize=LRU_CACHE_SIZE) def coastlines( @@ -123,7 +127,7 @@ def load_coastline_geometries( fname = shp.natural_earth(resolution=resolution, category=category, name=name) reader = shp.Reader(fname) - def unpack(geometries): + def unpack(geometries: Geometries) -> None: for geometry in geometries: if isinstance(geometry, MultiLineString): multi_lines.extend(list(geometry.geoms)) diff --git a/src/geovista/geoplotter.py b/src/geovista/geoplotter.py index 6a1b812c..519ccd84 100644 --- a/src/geovista/geoplotter.py +++ b/src/geovista/geoplotter.py @@ -10,13 +10,12 @@ from __future__ import annotations from functools import lru_cache -from typing import Any +from typing import TYPE_CHECKING, Any from warnings import warn -import numpy.typing as npt from pyproj import CRS import pyvista as pv -import pyvista.core.utilities.helpers as helpers +from pyvista.core.utilities import helpers from .bridge import Transform from .common import ( @@ -54,6 +53,9 @@ from .samples import LFRIC_RESOLUTION, REGULAR_RESOLUTION, lfric, regular_grid from .transform import transform_mesh +if TYPE_CHECKING: + import numpy.typing as npt + __all__ = ["GeoPlotter"] #: The valid 'style' options for adding points. @@ -335,15 +337,12 @@ def add_base_layer( if mesh is not None: if radius is not None: mesh = resize(mesh, radius=radius) + elif resolution.startswith("r"): + mesh = regular_grid(resolution=resolution, radius=radius) else: - if resolution.startswith("r"): - mesh = regular_grid(resolution=resolution, radius=radius) - else: - mesh = _lfric_mesh(resolution=resolution, radius=radius) - - actor = self.add_mesh(mesh, **kwargs) + mesh = _lfric_mesh(resolution=resolution, radius=radius) - return actor + return self.add_mesh(mesh, **kwargs) def add_coastlines( self, @@ -411,9 +410,7 @@ def add_coastlines( if atol is not None: kwargs["atol"] = atol - actor = self.add_mesh(mesh, **kwargs) - - return actor + return self.add_mesh(mesh, **kwargs) def add_graticule( self, @@ -618,12 +615,11 @@ def add_mesh(self, mesh: Any, **kwargs: Any | None) -> pv.Actor: inplace=not cloud, ) to_wkt(mesh, self.crs) - else: - if not projected(mesh) and zlevel: - if not cloud: - radius = distance(mesh) + elif not projected(mesh) and zlevel: + if not cloud: + radius = distance(mesh) - mesh = resize(mesh, radius=radius, zlevel=zlevel, zscale=zscale) + mesh = resize(mesh, radius=radius, zlevel=zlevel, zscale=zscale) def _check(option: str) -> bool: return option in kwargs and kwargs[option] is not None @@ -943,7 +939,7 @@ def add_parallels( if point_labels_args is None: point_labels_args = {} - # TODO: fix behaviour of longitudes at poles + # TODO @bjlittle: Fix behaviour of longitudes at poles. poles_parallel = False parallels = create_parallels( diff --git a/src/geovista/gridlines.py b/src/geovista/gridlines.py index 6d4ff717..a3c10a4a 100644 --- a/src/geovista/gridlines.py +++ b/src/geovista/gridlines.py @@ -9,9 +9,9 @@ from collections.abc import Iterable from dataclasses import dataclass +from typing import TYPE_CHECKING import numpy as np -from numpy.typing import ArrayLike import pyvista as pv from .common import ( @@ -25,6 +25,9 @@ ) from .crs import WGS84, to_wkt +if TYPE_CHECKING: + from numpy.typing import ArrayLike + __all__ = [ "GRATICULE_CLOSED_INTERVAL", "GRATICULE_ZLEVEL", @@ -163,17 +166,17 @@ def create_meridian_labels(lons: list[float]) -> list[str]: lons = [lons] for lon in lons: - # explicit truncation, perhaps offer format control when required - lon = int(lon) + # TODO @bjlittle: Explicit truncation is performed, perhaps offer format + # control when required. + value = int(lon) direction = LABEL_EAST - if lon == 0 or np.isclose(np.abs(lon), 180): + if value == 0 or np.isclose(np.abs(value), 180): direction = LABEL_DEGREE - elif lon < 0: + elif value < 0: direction = LABEL_WEST - value = np.abs(lon) - result.append(f"{value}{direction}") + result.append(f"{np.abs(value)}{direction}") return result @@ -313,7 +316,7 @@ def create_meridians( mesh.point_data[GV_REMESH_POINT_IDS] = seam mesh.set_active_scalars(name=None) - blocks[f"{index},{str(lon)}"] = mesh + blocks[f"{index},{lon!r}"] = mesh grid_points, grid_labels = [], [] labels = create_meridian_labels(list(lons)) @@ -331,12 +334,10 @@ def create_meridians( if mask is not None: mask = np.tile(mask, grid_lats.size) - graticule = GraticuleGrid( + return GraticuleGrid( blocks=blocks, lonlat=grid_points, labels=grid_labels, mask=mask ) - return graticule - def create_parallel_labels( lats: list[float], poles_parallel: bool | None = None @@ -372,19 +373,18 @@ def create_parallel_labels( for lat in lats: # explicit truncation, perhaps offer format control when required - lat = int(lat) + value = int(lat) direction = LABEL_NORTH - if lat == 0: + if value == 0: direction = LABEL_DEGREE - elif lat < 0: + elif value < 0: direction = LABEL_SOUTH - if not poles_parallel and np.isclose(np.abs(lat), 90): + if not poles_parallel and np.isclose(np.abs(value), 90): continue - value = np.abs(lat) - result.append(f"{value}{direction}") + result.append(f"{np.abs(value)}{direction}") return result @@ -509,7 +509,7 @@ def create_parallels( mesh = pv.PolyData(xyz, lines=lines, n_lines=n_points) to_wkt(mesh, WGS84) - blocks[f"{index},{str(lat)}"] = mesh + blocks[f"{index},{lat!r}"] = mesh grid_lats.append(lat) grid_points, grid_labels = [], [] @@ -539,6 +539,5 @@ def create_parallels( grid_labels.extend(pole_labels) grid_points = np.vstack(grid_points) - graticule = GraticuleGrid(blocks=blocks, lonlat=grid_points, labels=grid_labels) - return graticule + return GraticuleGrid(blocks=blocks, lonlat=grid_points, labels=grid_labels) diff --git a/src/geovista/pantry.py b/src/geovista/pantry.py index 6e08b20e..c8a41789 100644 --- a/src/geovista/pantry.py +++ b/src/geovista/pantry.py @@ -12,16 +12,19 @@ from dataclasses import dataclass, field from enum import Enum from functools import lru_cache +from typing import TYPE_CHECKING -import netCDF4 as nc +import netCDF4 as nc # noqa: N813 import numpy as np from numpy import ma -from numpy.typing import ArrayLike import pooch from .cache import CACHE from .common import LRU_CACHE_SIZE, _MixinStrEnum +if TYPE_CHECKING: + from numpy.typing import ArrayLike + __all__ = [ "CLOUD_AMOUNT_PREFERENCE", "CloudPreference", @@ -51,7 +54,7 @@ CLOUD_AMOUNT_PREFERENCE: str = "mesh" -# TODO: use StrEnum and auto when minimum supported python version is 3.11 +# TODO @bjlittle: Use StrEnum and auto when minimum supported python version is 3.11. class CloudPreference(_MixinStrEnum, Enum): """Enumeration of mesh types for cloud amount. @@ -131,9 +134,8 @@ def capitalise(title: str) -> str: """ title = title.replace("_", " ") title = title.split(" ") - title = " ".join([word.capitalize() for word in title]) - return title + return " ".join([word.capitalize() for word in title]) def _cloud_amount_dataset(fname: str | CloudPreference) -> nc.Dataset: @@ -157,9 +159,8 @@ def _cloud_amount_dataset(fname: str | CloudPreference) -> nc.Dataset: """ processor = pooch.Decompress(method="auto", name=fname) resource = CACHE.fetch(f"pantry/c768/{fname}.bz2", processor=processor) - dataset = nc.Dataset(resource) - return dataset + return nc.Dataset(resource) @lru_cache(maxsize=LRU_CACHE_SIZE) @@ -220,7 +221,7 @@ def cloud_amount( units = data.units data = data[:][0] - sample = SampleUnstructuredXY( + return SampleUnstructuredXY( lons, lats, connectivity[:], @@ -230,8 +231,6 @@ def cloud_amount( units=units, ) - return sample - @lru_cache(maxsize=LRU_CACHE_SIZE) def fesom(step: int | None = None) -> SampleUnstructuredXY: @@ -293,12 +292,10 @@ def fesom(step: int | None = None) -> SampleUnstructuredXY: connectivity = ma.arange(np.prod(shape), dtype=np.uint32).reshape(shape) connectivity.mask = mask - sample = SampleUnstructuredXY( + return SampleUnstructuredXY( lons, lats, connectivity, data=data[idx], name=name, units=units, steps=steps ) - return sample - @lru_cache(maxsize=LRU_CACHE_SIZE) def fvcom_tamar() -> SampleUnstructuredXY: @@ -335,7 +332,7 @@ def fvcom_tamar() -> SampleUnstructuredXY: units = face.units node = dataset.variables["h"][:] - sample = SampleUnstructuredXY( + return SampleUnstructuredXY( lons, lats, connectivity.T, @@ -346,8 +343,6 @@ def fvcom_tamar() -> SampleUnstructuredXY: units=units, ) - return sample - @lru_cache(maxsize=LRU_CACHE_SIZE) def icon_soil() -> SampleUnstructuredXY: @@ -386,12 +381,10 @@ def icon_soil() -> SampleUnstructuredXY: name = capitalise("soil type") units = "1" - sample = SampleUnstructuredXY( + return SampleUnstructuredXY( lons, lats, lons.shape, data=data, name=name, units=units ) - return sample - @lru_cache(maxsize=LRU_CACHE_SIZE) def icosahedral() -> SampleUnstructuredXY: @@ -423,12 +416,10 @@ def icosahedral() -> SampleUnstructuredXY: name = capitalise(data.long_name) units = data.units - sample = SampleUnstructuredXY( + return SampleUnstructuredXY( lons, lats, lons.shape, data=data, name=name, units=units ) - return sample - @lru_cache(maxsize=LRU_CACHE_SIZE) def _gungho_lam(fname: str) -> SampleUnstructuredXY: @@ -463,9 +454,7 @@ def _gungho_lam(fname: str) -> SampleUnstructuredXY: connectivity = dataset.variables["gungho_face_nodes"] start_index = connectivity.start_index - sample = SampleUnstructuredXY(lons, lats, connectivity[:], start_index=start_index) - - return sample + return SampleUnstructuredXY(lons, lats, connectivity[:], start_index=start_index) def lam_equator() -> SampleUnstructuredXY: @@ -578,7 +567,7 @@ def lam_pacific() -> SampleUnstructuredXY: name = capitalise(data.standard_name) units = data.units - sample = SampleUnstructuredXY( + return SampleUnstructuredXY( lons, lats, connectivity[:], @@ -588,8 +577,6 @@ def lam_pacific() -> SampleUnstructuredXY: units=units, ) - return sample - def lam_polar() -> SampleUnstructuredXY: """Download and cache unstructured surface sample data. @@ -662,7 +649,7 @@ def lfric_orog() -> SampleUnstructuredXY: name = capitalise(data.standard_name) units = data.units - sample = SampleUnstructuredXY( + return SampleUnstructuredXY( lons, lats, connectivity[:], @@ -672,8 +659,6 @@ def lfric_orog() -> SampleUnstructuredXY: units=units, ) - return sample - @lru_cache(maxsize=LRU_CACHE_SIZE) def lfric_sst() -> SampleUnstructuredXY: @@ -709,7 +694,7 @@ def lfric_sst() -> SampleUnstructuredXY: name = capitalise(data.standard_name) units = data.units - sample = SampleUnstructuredXY( + return SampleUnstructuredXY( lons, lats, connectivity[:], @@ -719,8 +704,6 @@ def lfric_sst() -> SampleUnstructuredXY: units=units, ) - return sample - @lru_cache(maxsize=LRU_CACHE_SIZE) def oisst_avhrr_sst() -> SampleStructuredXY: @@ -752,9 +735,7 @@ def oisst_avhrr_sst() -> SampleStructuredXY: name = capitalise(data.long_name) units = data.units - sample = SampleStructuredXY(lons, lats, data=data[0, 0], name=name, units=units) - - return sample + return SampleStructuredXY(lons, lats, data=data[0, 0], name=name, units=units) @lru_cache(maxsize=LRU_CACHE_SIZE) @@ -787,9 +768,7 @@ def um_orca2() -> SampleStructuredXY: name = capitalise(data.standard_name) units = data.units - sample = SampleStructuredXY(lons, lats, data=data[0, 0], name=name, units=units) - - return sample + return SampleStructuredXY(lons, lats, data=data[0, 0], name=name, units=units) @lru_cache(maxsize=LRU_CACHE_SIZE) @@ -821,9 +800,7 @@ def um_orca2_gradient() -> SampleStructuredXYZ: depth = depth[:] name = "Depth" - sample = SampleStructuredXYZ(lons, lats, depth, data=depth, name=name, units=units) - - return sample + return SampleStructuredXYZ(lons, lats, depth, data=depth, name=name, units=units) @lru_cache(maxsize=LRU_CACHE_SIZE) @@ -866,9 +843,7 @@ def usgs_earthquakes() -> SampleStructuredXYZ: zlevel = dataset.depth.to_numpy() data = dataset.mag.to_numpy() - sample = SampleStructuredXYZ(lons=lons, lats=lats, zlevel=zlevel, data=data) - - return sample + return SampleStructuredXYZ(lons=lons, lats=lats, zlevel=zlevel, data=data) @lru_cache(maxsize=LRU_CACHE_SIZE) @@ -928,12 +903,10 @@ def ww3_global_smc(step: int | None = None) -> SampleUnstructuredXY: name = capitalise(data.standard_name) units = data.units - sample = SampleUnstructuredXY( + return SampleUnstructuredXY( lons, lats, lons.shape, data=data[idx], name=name, units=units, steps=steps ) - return sample - @lru_cache(maxsize=LRU_CACHE_SIZE) def ww3_global_tri() -> SampleUnstructuredXY: @@ -972,7 +945,7 @@ def ww3_global_tri() -> SampleUnstructuredXY: name = capitalise(data.standard_name) units = data.units - sample = SampleUnstructuredXY( + return SampleUnstructuredXY( lons, lats, connectivity, @@ -981,5 +954,3 @@ def ww3_global_tri() -> SampleUnstructuredXY: name=name, units=units, ) - - return sample diff --git a/src/geovista/report.py b/src/geovista/report.py index ffdddcaa..d98d0310 100644 --- a/src/geovista/report.py +++ b/src/geovista/report.py @@ -117,8 +117,8 @@ def __init__( if gpu: try: extra_meta = pyvista.GPUInfo().get_info() - except: - # XXX: bare except required in order to handle rendering faults + except: # noqa: E722 + # bare except required in order to handle rendering faults extra_meta = [ ("GPU Details", "Error"), ] diff --git a/src/geovista/samples.py b/src/geovista/samples.py index dede7697..1fdec110 100644 --- a/src/geovista/samples.py +++ b/src/geovista/samples.py @@ -86,15 +86,13 @@ def _lfric_sample_to_mesh(sample: pantry.SampleUnstructuredXY) -> pv.PolyData: .. versionadded:: 0.1.0 """ - mesh = Transform.from_unstructured( + return Transform.from_unstructured( sample.lons, sample.lats, connectivity=sample.connectivity, start_index=sample.start_index, ) - return mesh - def cloud_amount(preference: str | pantry.CloudPreference | None = None) -> pv.PolyData: """Create a mesh from :mod:`geovista.pantry` sample data. @@ -155,7 +153,7 @@ def fesom() -> pv.PolyData: """ sample = pantry.fesom() - mesh = Transform.from_unstructured( + return Transform.from_unstructured( sample.lons, sample.lats, connectivity=sample.connectivity, @@ -163,8 +161,6 @@ def fesom() -> pv.PolyData: name=sample.name, ) - return mesh - def fvcom_tamar( preference: str | Preference | None = None, @@ -252,15 +248,13 @@ def icon_soil() -> pv.PolyData: """ sample = pantry.icon_soil() - mesh = Transform.from_unstructured( + return Transform.from_unstructured( sample.lons, sample.lats, data=sample.data, name=sample.name, ) - return mesh - def icosahedral() -> pv.PolyData: """Create a mesh from :mod:`geovista.pantry` sample data. @@ -279,15 +273,13 @@ def icosahedral() -> pv.PolyData: """ sample = pantry.icosahedral() - mesh = Transform.from_unstructured( + return Transform.from_unstructured( sample.lons, sample.lats, data=sample.data, name=sample.name, ) - return mesh - def lam_equator() -> pv.PolyData: """Create a mesh from :mod:`geovista.pantry` sample data. @@ -377,7 +369,7 @@ def lam_pacific() -> pv.PolyData: """ sample = pantry.lam_pacific() - mesh = Transform.from_unstructured( + return Transform.from_unstructured( sample.lons, sample.lats, connectivity=sample.connectivity, @@ -386,8 +378,6 @@ def lam_pacific() -> pv.PolyData: start_index=sample.start_index, ) - return mesh - def lam_polar() -> pv.PolyData: """Create a mesh from :mod:`geovista.pantry` sample data. @@ -461,9 +451,8 @@ def lfric(resolution: str | None = None) -> pv.PolyData: fname = f"lfric_{resolution}.vtk" processor = pooch.Decompress(method="auto", name=fname) resource = CACHE.fetch(f"mesh/{fname}.bz2", processor=processor) - mesh = pv.read(resource) - return mesh + return pv.read(resource) def lfric_orog(warp: bool | None = False, factor: float | None = None) -> pv.PolyData: @@ -528,7 +517,7 @@ def lfric_sst() -> pv.PolyData: """ sample = pantry.lfric_sst() - mesh = Transform.from_unstructured( + return Transform.from_unstructured( sample.lons, sample.lats, connectivity=sample.connectivity, @@ -537,8 +526,6 @@ def lfric_sst() -> pv.PolyData: start_index=sample.start_index, ) - return mesh - def regular_grid( resolution: str | None = None, @@ -590,8 +577,8 @@ def warn_unknown() -> None: lats = np.linspace(-90.0, 90.0, n_cells + 1) lons = np.linspace(-180.0, 180.0, int(n_cells * 1.5) + 1) - mesh = Transform.from_1d(lons, lats, radius=radius) - return mesh + + return Transform.from_1d(lons, lats, radius=radius) def oisst_avhrr_sst() -> pv.PolyData: @@ -611,15 +598,13 @@ def oisst_avhrr_sst() -> pv.PolyData: """ sample = pantry.oisst_avhrr_sst() - mesh = Transform.from_1d( + return Transform.from_1d( sample.lons, sample.lats, data=sample.data, name=sample.name, ) - return mesh - def um_orca2() -> pv.PolyData: """Create a mesh from :mod:`geovista.pantry` sample data. @@ -638,12 +623,10 @@ def um_orca2() -> pv.PolyData: """ sample = pantry.um_orca2() - mesh = Transform.from_2d( + return Transform.from_2d( sample.lons, sample.lats, data=sample.data, name=sample.name ) - return mesh - def um_orca2_cloud(zscale: float | None = None) -> pv.PolyData: """Create a point-cloud mesh from :mod:`geovista.pantry` sample data. @@ -670,7 +653,7 @@ def um_orca2_cloud(zscale: float | None = None) -> pv.PolyData: zscale = ZLEVEL_SCALE_CLOUD if zscale is None else float(zscale) - cloud = Transform.from_points( + return Transform.from_points( sample.lons, sample.lats, data=sample.zlevel, @@ -679,8 +662,6 @@ def um_orca2_cloud(zscale: float | None = None) -> pv.PolyData: zscale=zscale, ) - return cloud - def ww3_global_smc(step: int | None = None) -> pv.PolyData: """Create a mesh from :mod:`geovista.pantry` sample data. @@ -705,7 +686,7 @@ def ww3_global_smc(step: int | None = None) -> pv.PolyData: """ sample = pantry.ww3_global_smc(step=step) - mesh = Transform.from_unstructured( + return Transform.from_unstructured( sample.lons, sample.lats, connectivity=sample.connectivity, @@ -713,8 +694,6 @@ def ww3_global_smc(step: int | None = None) -> pv.PolyData: name=sample.name, ) - return mesh - def ww3_global_tri() -> pv.PolyData: """Create a mesh from :mod:`geovista.pantry` sample data. @@ -734,12 +713,10 @@ def ww3_global_tri() -> pv.PolyData: """ sample = pantry.ww3_global_tri() - mesh = Transform.from_unstructured( + return Transform.from_unstructured( sample.lons, sample.lats, connectivity=sample.connectivity, data=sample.data, name=sample.name, ) - - return mesh diff --git a/src/geovista/search.py b/src/geovista/search.py index de1e36ac..dd852a72 100644 --- a/src/geovista/search.py +++ b/src/geovista/search.py @@ -9,16 +9,19 @@ from collections.abc import Iterable from enum import Enum +from typing import TYPE_CHECKING import numpy as np from numpy.typing import ArrayLike from pykdtree.kdtree import KDTree as pyKDTree -from pyvista import PolyData from .common import _MixinStrEnum, to_cartesian from .crs import WGS84, from_wkt from .transform import transform_points +if TYPE_CHECKING: + from pyvista import PolyData + __all__ = ["KDTree", "Preference", "find_cell_neighbours", "find_nearest_cell"] # type aliases @@ -39,7 +42,7 @@ KDTREE_PREFERENCE: str = "point" -# TODO: use StrEnum and auto when minimum supported python version is 3.11 +# TODO @bjlittle: Use StrEnum and auto when minimum supported python version is 3.11. class Preference(_MixinStrEnum, Enum): """Enumeration of mesh geometry preferences. @@ -129,14 +132,14 @@ def __init__( transformed = transform_points( src_crs=crs, tgt_crs=WGS84, xs=xyz[:, 0], ys=xyz[:, 1] ) - # TODO: clarify zlevel preservation for non-WGS84 point-clouds + # TODO @bjlittle: Clarify zlevel preservation for non-WGS84 point-clouds. xyz = to_cartesian(transformed[:, 0], transformed[:, 1]) self._n_points = xyz.shape[0] self._mesh_type = mesh.__class__.__name__ self._kdtree = pyKDTree(xyz, leafsize=leaf_size) - def __repr__(self): + def __repr__(self) -> str: """Serialize kd-tree representation. Returns @@ -272,12 +275,11 @@ def query( epsilon = KDTREE_EPSILON xyz = to_cartesian(lons, lats, radius=radius, zlevel=zlevel, zscale=zscale) - result = self._kdtree.query( + + return self._kdtree.query( xyz, k=k, eps=epsilon, distance_upper_bound=distance_upper_bound ) - return result - def find_cell_neighbours(mesh: PolyData, cid: CellIDLike) -> CellIDs: """Find all the cells neighbouring the given `cid` cell/s of the `mesh`. @@ -308,7 +310,7 @@ def find_cell_neighbours(mesh: PolyData, cid: CellIDLike) -> CellIDs: pids = [] for idx in cid: - # XXX: pyvista 0.38.0: cell_point_ids(idx) -> get_cell(idx).point_ids + # NOTE: pyvista 0.38.0: cell_point_ids(idx) -> get_cell(idx).point_ids pids.extend(mesh.get_cell(idx).point_ids) # determine the unique points @@ -368,7 +370,7 @@ def find_nearest_cell( poi = to_cartesian(x, y)[0] if crs in [WGS84, None] else (x, y, z) cid = mesh.find_closest_cell(poi) - # XXX: pyvista 0.38.0: cell_point_ids(cid) -> get_cell(cid).point_ids + # NOTE: pyvista 0.38.0: cell_point_ids(cid) -> get_cell(cid).point_ids pids = np.asanyarray(mesh.get_cell(cid).point_ids) points = mesh.points[pids] mask = np.all(np.isclose(points, poi), axis=1) @@ -382,9 +384,7 @@ def find_nearest_cell( if single: if (count := len(result)) > 1: - emsg = ( - f"Expected to find 1 cell but found {count}, " f"got CellIDs {result}." - ) + emsg = f"Expected to find 1 cell but found {count}, got CellIDs {result}." raise ValueError(emsg) (result,) = result diff --git a/src/geovista/transform.py b/src/geovista/transform.py index df32e707..aaaaa013 100644 --- a/src/geovista/transform.py +++ b/src/geovista/transform.py @@ -8,11 +8,10 @@ from __future__ import annotations from copy import deepcopy +from typing import TYPE_CHECKING import numpy as np -from numpy.typing import ArrayLike from pyproj import CRS, Transformer -import pyvista as pv from .common import GV_FIELD_ZSCALE, ZLEVEL_SCALE, from_cartesian, point_cloud from .crs import ( @@ -24,6 +23,11 @@ to_wkt, ) +if TYPE_CHECKING: + from numpy.typing import ArrayLike + import pyvista as pv + + __all__ = [ "transform_mesh", "transform_point", @@ -147,9 +151,10 @@ def transform_mesh( if zlevel or cloud: xmin, xmax, ymin, ymax, _, _ = mesh.bounds xdelta, ydelta = abs(xmax - xmin), abs(ymax - ymin) - # TODO: make this scale factor configurable at the API/module level - # current strategy is slightly flawed in that there isn't consistent - # scaling across all geometries added to the render + # TODO @bjlittle: Make this scale factor configurable at the API/module + # level, as current strategy is slightly flawed in that + # there isn't consistent scaling across all geometries + # added to the render scene. delta = max(xdelta, ydelta) // 4 if cloud: @@ -160,7 +165,7 @@ def transform_mesh( mesh.points[:, 2] = zs - # TODO: check whether to clean other field_data metadata + # TODO @bjlittle: Check whether to clean other field_data metadata. to_wkt(mesh, original_tgt_crs) return mesh @@ -169,9 +174,9 @@ def transform_mesh( def transform_point( src_crs: CRSLike, tgt_crs: CRSLike, - x: int | float, - y: int | float, - z: int | float | None = None, + x: float, + y: float, + z: float | None = None, trap: bool | None = True, ) -> ArrayLike: """Transform the spatial point from the source to the target CRS. diff --git a/tests/bridge/test__as_compatible_data.py b/tests/bridge/test__as_compatible_data.py index e78f8300..3dc41a4f 100644 --- a/tests/bridge/test__as_compatible_data.py +++ b/tests/bridge/test__as_compatible_data.py @@ -2,7 +2,7 @@ from __future__ import annotations import numpy as np -import numpy.ma as ma +from numpy import ma import pytest from geovista.bridge import Transform diff --git a/tests/common/conftest.py b/tests/common/conftest.py index 8a4971fc..db13e47d 100644 --- a/tests/common/conftest.py +++ b/tests/common/conftest.py @@ -59,7 +59,7 @@ def degrees(request): return request.param -@pytest.fixture +@pytest.fixture() def radians(degrees): """Fixture for testing single value from cartesian to geographic radians.""" return Convert(degrees.xyz, np.radians(degrees.expected)) @@ -71,7 +71,7 @@ def manydegrees(request): return request.param -@pytest.fixture +@pytest.fixture() def manyradians(manydegrees): """Fixture for testing multiple values from cartesian to geographic radians.""" return Convert(manydegrees.xyz, np.radians(np.array(manydegrees.expected))) diff --git a/tests/common/test_Preference.py b/tests/common/test_Preference.py index ce3b90dc..877fdc39 100644 --- a/tests/common/test_Preference.py +++ b/tests/common/test_Preference.py @@ -24,7 +24,7 @@ def test_values(): @pytest.mark.parametrize( - "member, expected", + ("member", "expected"), [ ("cell", True), ("Cell", True), diff --git a/tests/common/test_distance.py b/tests/common/test_distance.py index ead1d928..11e54512 100644 --- a/tests/common/test_distance.py +++ b/tests/common/test_distance.py @@ -24,7 +24,11 @@ def test_mean_distance(lfric, scale): @pytest.mark.parametrize( - "origin", [np.random.randint(0, high=10, size=3).astype(float) for _ in range(10)] + "origin", + [ + np.random.default_rng().integers(0, high=10, size=3).astype(float) + for _ in range(10) + ], ) def test_mean_distance__origin(lfric, origin): """Test mean distance with mesh translated to random origin.""" @@ -44,7 +48,11 @@ def test_point_distance(lfric, scale): @pytest.mark.parametrize( - "origin", [np.random.randint(0, high=10, size=3).astype(float) for _ in range(10)] + "origin", + [ + np.random.default_rng().integers(0, high=10, size=3).astype(float) + for _ in range(10) + ], ) def test_point_distance__origin(lfric, origin): """Test point distance with mesh translated to random origin.""" diff --git a/tests/common/test_from_cartesian.py b/tests/common/test_from_cartesian.py index 5a52ebfb..16eadb5c 100644 --- a/tests/common/test_from_cartesian.py +++ b/tests/common/test_from_cartesian.py @@ -92,20 +92,20 @@ def test_polar_quad_mesh_unfold(sign, n_lons, closed_interval): @pytest.mark.parametrize("closed_interval", [False, True]) @pytest.mark.parametrize( - "lonlat, pids", + ("lonlat", "pids"), [ - [ + ( np.array([[170, 10], [180, 10], [-180, 0], [-170, 0]]), np.array([[0, 1], [2, 3]]), - ], - [ + ), + ( np.array([[170, 0], [180, 0], [-170, 0]]), np.array([[0, 1], [1, 2]]), - ], - [ + ), + ( np.array([[170, 0], [180, 0], [-180, 0], [-170, 0]]), np.array([[0, 1], [2, 3]]), - ], + ), ], ) def test_lines_closed_interval(closed_interval, lonlat, pids): diff --git a/tests/common/test_nan_mask.py b/tests/common/test_nan_mask.py index 9b499ca9..faccb9db 100644 --- a/tests/common/test_nan_mask.py +++ b/tests/common/test_nan_mask.py @@ -2,7 +2,7 @@ from __future__ import annotations import numpy as np -import numpy.ma as ma +from numpy import ma import pytest from geovista.common import nan_mask diff --git a/tests/common/test_sanitize_data.py b/tests/common/test_sanitize_data.py index 93c41d50..54f9b335 100644 --- a/tests/common/test_sanitize_data.py +++ b/tests/common/test_sanitize_data.py @@ -1,9 +1,13 @@ """Unit-tests for :func:`geovista.common.sanitize_data`.""" from __future__ import annotations +from typing import TYPE_CHECKING + import numpy as np import pytest -import pyvista as pv + +if TYPE_CHECKING: + import pyvista as pv from geovista.common import VTK_CELL_IDS, VTK_POINT_IDS, sanitize_data diff --git a/tests/common/test_to_cartesian.py b/tests/common/test_to_cartesian.py index adeb718a..55237813 100644 --- a/tests/common/test_to_cartesian.py +++ b/tests/common/test_to_cartesian.py @@ -15,8 +15,8 @@ def _distance(pts: npt.ArrayLike, stacked: bool = True) -> float: if not stacked: pts = np.transpose(pts) nrow, ncol = pts.shape - result = np.sqrt(np.sum(pts.T @ pts * np.identity(ncol)) / nrow) - return result + + return np.sqrt(np.sum(pts.T @ pts * np.identity(ncol)) / nrow) def test_shape_fail(): @@ -61,7 +61,7 @@ def test_zlevel__scalar(lam_uk_sample, zlevel): @pytest.mark.parametrize( - "xy_reshape, z_reshape", [((-1,), (-1, 1)), ((1, 5, 5), (-1, 1, 1))] + ("xy_reshape", "z_reshape"), [((-1,), (-1, 1)), ((1, 5, 5), (-1, 1, 1))] ) @pytest.mark.parametrize("n_levels", range(3, 11)) def test_zlevel__broadcast(lam_uk_sample, xy_reshape, z_reshape, n_levels): diff --git a/tests/common/test_to_lonlat.py b/tests/common/test_to_lonlat.py index c2b7964c..31a693cc 100644 --- a/tests/common/test_to_lonlat.py +++ b/tests/common/test_to_lonlat.py @@ -8,7 +8,7 @@ @pytest.mark.parametrize( - "point, emsg", + ("point", "emsg"), [ (0, r"Require a 1-D array .* got a 0-D array"), ([0], r"Require a 1-D array .* got a 1-D array with shape \(1,\)"), diff --git a/tests/common/test_to_lonlats.py b/tests/common/test_to_lonlats.py index cd1f9072..e2e8ec5a 100644 --- a/tests/common/test_to_lonlats.py +++ b/tests/common/test_to_lonlats.py @@ -8,7 +8,7 @@ @pytest.mark.parametrize( - "points, emsg", + ("points", "emsg"), [ ([[[0]]], r"Require a 2-D array .* got a 3-D array"), ([[0, 1]], r"Require a 2-D array .* got a 2-D array with shape \(1, 2\)"), diff --git a/tests/common/test_wrap.py b/tests/common/test_wrap.py index 81ff033e..e8cf9b86 100644 --- a/tests/common/test_wrap.py +++ b/tests/common/test_wrap.py @@ -101,12 +101,11 @@ def test_base0__dtype(base0, dtype): @pytest.mark.parametrize( - "delta, expected", [(1.0e-4, True), (1.0e-3, True), (2.0e-3, False)] + ("delta", "expected"), [(1.0e-4, True), (1.0e-3, True), (2.0e-3, False)] ) def test_base_period_tolerance(delta, expected): """Test the relative and absolute tolerance of the base + period value.""" result = wrap(180 - delta) - print(result, np.isclose(result, -180)) assert np.isclose(result, -180)[0] == expected diff --git a/tests/conftest.py b/tests/conftest.py index b9a351a7..a83fab67 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,17 +14,17 @@ from geovista.samples import lfric_sst as sample_lfric_sst -@pytest.fixture +@pytest.fixture() def coastlines(request): """Fixture generates a coastlines line mesh.""" # support indirect parameters for fixtures and also # calling the fixture with no parameter resolution = request.param if hasattr(request, "param") else "110m" - mesh = geometry_coastlines(resolution=resolution) - return mesh + + return geometry_coastlines(resolution=resolution) -@pytest.fixture +@pytest.fixture() def lam_uk(): """Fixture generates a Local Area Model mesh with indexed faces and points.""" mesh = sample_lam_uk() @@ -37,8 +37,8 @@ def lam_uk(): def lam_uk_cloud(lam_uk_sample): """Fixture generates a Local Area Model point-could for the UK.""" lons, lats = lam_uk_sample - cloud = Transform.from_points(lons, lats) - return cloud + + return Transform.from_points(lons, lats) @pytest.fixture(scope="session") @@ -48,14 +48,14 @@ def lam_uk_sample(): return sample.lons[:], sample.lats[:] -@pytest.fixture +@pytest.fixture() def lfric(request): """Fixture to provide a cube-sphere mesh.""" # support indirect parameters for fixtures and also # calling the fixture with no parameter resolution = request.param if hasattr(request, "param") else "c48" - mesh = sample_lfric(resolution=resolution) - return mesh + + return sample_lfric(resolution=resolution) @pytest.fixture(scope="session") @@ -75,7 +75,7 @@ def sphere(): return pv.Sphere() -@pytest.fixture +@pytest.fixture() def wgs84_wkt(): """Fixture for generating WG284 CRS WKT as a string.""" return WGS84.to_wkt() diff --git a/tests/core/test_slice_lines.py b/tests/core/test_slice_lines.py index 18befb53..5a97f69a 100644 --- a/tests/core/test_slice_lines.py +++ b/tests/core/test_slice_lines.py @@ -22,8 +22,8 @@ class Kind: detach: int = 0 -# TODO: add further examples of geovista projected meshes, -# this requires "transform_mesh" API support +# TODO @bjlittle: Add further examples of geovista projected meshes, this requires +# "transform_mesh" API support. @pytest.mark.parametrize("mesh", [pv.Plane()]) def test_projected_fail(mesh): """Test trap of a mesh that is projected.""" @@ -114,16 +114,16 @@ def antimeridian_count(mesh: pv.PolyData) -> int: @pytest.mark.parametrize( - "lonlat, pids, kind", + ("lonlat", "pids", "kind"), [ - [np.array([[179, 0], [181, 0]]), np.array([[0, 1]]), Kind(split=1, detach=0)], - [ + (np.array([[179, 0], [181, 0]]), np.array([[0, 1]]), Kind(split=1, detach=0)), + ( np.array([[179, 0], [180, 0], [181, 0]]), np.array([[0, 1], [1, 2]]), Kind(split=0, detach=1), - ], - [np.array([[179, 0], [180, 0]]), np.array([[0, 1]]), Kind(split=0, detach=0)], - [np.array([[180, 0], [181, 0]]), np.array([[0, 1]]), Kind(split=0, detach=1)], + ), + (np.array([[179, 0], [180, 0]]), np.array([[0, 1]]), Kind(split=0, detach=0)), + (np.array([[180, 0], [181, 0]]), np.array([[0, 1]]), Kind(split=0, detach=1)), ], ) def test_slice_lines(lonlat, pids, kind): @@ -170,6 +170,6 @@ def test_field_data(coastlines): metadata = dict(coastlines.field_data.items()) result = slice_lines(coastlines) assert set(result.field_data.keys()) == set(metadata.keys()) - for key in metadata.keys(): + for key in metadata: assert id(result.field_data[key]) != id(metadata[key]) np.testing.assert_array_equal(result.field_data[key], metadata[key]) diff --git a/tests/crs/test_has_wkt.py b/tests/crs/test_has_wkt.py index 00d3bdb0..b480edc0 100644 --- a/tests/crs/test_has_wkt.py +++ b/tests/crs/test_has_wkt.py @@ -6,7 +6,7 @@ from geovista.crs import has_wkt -@pytest.mark.parametrize("mesh, expected", [("sphere", False), ("lfric", True)]) +@pytest.mark.parametrize(("mesh", "expected"), [("sphere", False), ("lfric", True)]) def test(mesh, expected, request): """Test mesh wkt serialization availability.""" mesh = request.getfixturevalue(mesh) diff --git a/tests/geodesic/conftest.py b/tests/geodesic/conftest.py index 764caf25..93b0a09c 100644 --- a/tests/geodesic/conftest.py +++ b/tests/geodesic/conftest.py @@ -9,7 +9,7 @@ CIDS = [11520, 11567, 13776, 13823] -@pytest.fixture +@pytest.fixture() def antarctic_corners(lfric_sst): """Fixture generates lon/lats of cubed-sphere corner cells centers.""" cells = lfric_sst.extract_cells(CIDS) diff --git a/tests/geodesic/test_Preference.py b/tests/geodesic/test_Preference.py index de66607c..3ee0b6ca 100644 --- a/tests/geodesic/test_Preference.py +++ b/tests/geodesic/test_Preference.py @@ -24,7 +24,7 @@ def test_values(): @pytest.mark.parametrize( - "member, expected", + ("member", "expected"), [ ("cell", True), ("Cell", True), diff --git a/tests/geodesic/test_line.py b/tests/geodesic/test_line.py index 9449a987..ee110d16 100644 --- a/tests/geodesic/test_line.py +++ b/tests/geodesic/test_line.py @@ -17,7 +17,7 @@ @pytest.mark.parametrize( - "lons, lats", + ("lons", "lats"), [(range(10), range(20)), (list(range(10)), list(range(20)))], ) def test_lons_lats__size_unequal_fail(lons, lats): @@ -28,7 +28,7 @@ def test_lons_lats__size_unequal_fail(lons, lats): @pytest.mark.parametrize( - "lons, lats", + ("lons", "lats"), [(0, 1), ([0], [1])], ) def test_lons_lats__size_minimal_fail(lons, lats): @@ -47,7 +47,7 @@ def test_lons_lats__loop_minimal_fail(): @pytest.mark.parametrize( - "nsamples, npts", + ("nsamples", "npts"), [(2, None), (2, 64), (3, 128), (4, 256), (8, 512)], ) def test_npts(nsamples, npts): @@ -76,7 +76,7 @@ def test_contains_sample_points(lfric_sst, nsamples): @pytest.mark.parametrize( - "lons, lats", + ("lons", "lats"), [ (0, [90, 0, -90]), ([0], (90, 45, 0, -45, -90)), @@ -107,7 +107,7 @@ def test_zlevel(lons, zlevel): @pytest.mark.parametrize("zscale", np.linspace(-1, 1)) def test_zscale(lons, zscale): """Test line z-control with zscale.""" - result = line(180, [90, 0, -90], zscale=zscale) + result = line(lons, [90, 0, -90], zscale=zscale) actual = distance(result) expected = RADIUS + RADIUS * zscale assert np.isclose(actual, expected) diff --git a/tests/geoplotter/test_add_points.py b/tests/geoplotter/test_add_points.py index 758e9576..dfccc2a1 100644 --- a/tests/geoplotter/test_add_points.py +++ b/tests/geoplotter/test_add_points.py @@ -34,7 +34,7 @@ def test_duplicate_points_fail(mocker): _ = plotter.add_points(points=points, xs=xs, ys=ys) -@pytest.mark.parametrize("xs, ys", [(True, None), (None, True)]) +@pytest.mark.parametrize(("xs", "ys"), [(True, None), (None, True)]) def test_over_specified_points_fail(mocker, xs, ys): """Test trap of points and xs or ys provided.""" plotter = GeoPlotter() @@ -93,7 +93,7 @@ def test_texture_kwarg_pop(mocker, sphere): plotter.add_mesh.assert_called_once_with(sphere, style="points", scalars=None) -@pytest.mark.parametrize("xs, ys", [(None, True), (True, None)]) +@pytest.mark.parametrize(("xs", "ys"), [(None, True), (True, None)]) def test_xs_ys_incomplete_fail(xs, ys): """Test trap of missing spatial points.""" plotter = GeoPlotter() diff --git a/tests/gridlines/test__step_period.py b/tests/gridlines/test__step_period.py index dd1be6a4..4b3f1d55 100644 --- a/tests/gridlines/test__step_period.py +++ b/tests/gridlines/test__step_period.py @@ -11,24 +11,20 @@ def expected(value: float, period: float) -> tuple[float, float]: """Calculate expected value within period.""" count = abs(value) // period - if value >= 0: - result = value - (count * period) - else: - result = value + (count * period) - return result + return value - (count * period) if value >= 0 else value + (count * period) @pytest.mark.parametrize( - "lon, lat", + ("lon", "lat"), [ - [0, 0], - [LONGITUDE_STEP_PERIOD // 2, LATITUDE_STEP_PERIOD // 2], - [LONGITUDE_STEP_PERIOD, LATITUDE_STEP_PERIOD], - [LONGITUDE_STEP_PERIOD * 1.5, LATITUDE_STEP_PERIOD * 1.5], - [-LONGITUDE_STEP_PERIOD // 2, -LATITUDE_STEP_PERIOD // 2], - [-LONGITUDE_STEP_PERIOD, -LATITUDE_STEP_PERIOD], - [-LONGITUDE_STEP_PERIOD * 1.5, -LATITUDE_STEP_PERIOD * 1.5], + (0, 0), + (LONGITUDE_STEP_PERIOD // 2, LATITUDE_STEP_PERIOD // 2), + (LONGITUDE_STEP_PERIOD, LATITUDE_STEP_PERIOD), + (LONGITUDE_STEP_PERIOD * 1.5, LATITUDE_STEP_PERIOD * 1.5), + (-LONGITUDE_STEP_PERIOD // 2, -LATITUDE_STEP_PERIOD // 2), + (-LONGITUDE_STEP_PERIOD, -LATITUDE_STEP_PERIOD), + (-LONGITUDE_STEP_PERIOD * 1.5, -LATITUDE_STEP_PERIOD * 1.5), ], ) def test(lon, lat): diff --git a/tests/gridlines/test_create_meridian_labels.py b/tests/gridlines/test_create_meridian_labels.py index c2cc39c1..d43a8db7 100644 --- a/tests/gridlines/test_create_meridian_labels.py +++ b/tests/gridlines/test_create_meridian_labels.py @@ -18,18 +18,18 @@ def test_empty(): @pytest.mark.parametrize( - "value, expected", + ("value", "expected"), [ - [-180, f"180{LABEL_DEGREE}"], - [[-180], f"180{LABEL_DEGREE}"], - [-90, f"90{LABEL_WEST}"], - [[-90], f"90{LABEL_WEST}"], - [0, f"0{LABEL_DEGREE}"], - [[0], f"0{LABEL_DEGREE}"], - [90, f"90{LABEL_EAST}"], - [[90], f"90{LABEL_EAST}"], - [180, f"180{LABEL_DEGREE}"], - [[180], f"180{LABEL_DEGREE}"], + (-180, f"180{LABEL_DEGREE}"), + ([-180], f"180{LABEL_DEGREE}"), + (-90, f"90{LABEL_WEST}"), + ([-90], f"90{LABEL_WEST}"), + (0, f"0{LABEL_DEGREE}"), + ([0], f"0{LABEL_DEGREE}"), + (90, f"90{LABEL_EAST}"), + ([90], f"90{LABEL_EAST}"), + (180, f"180{LABEL_DEGREE}"), + ([180], f"180{LABEL_DEGREE}"), ], ) def test(value, expected): @@ -39,13 +39,13 @@ def test(value, expected): @pytest.mark.parametrize( - "value, expected", + ("value", "expected"), [ - [-180.1, f"180{LABEL_DEGREE}"], - [-90.12, f"90{LABEL_WEST}"], - [0.123, f"0{LABEL_DEGREE}"], - [90.1234, f"90{LABEL_EAST}"], - [180.12345, f"180{LABEL_DEGREE}"], + (-180.1, f"180{LABEL_DEGREE}"), + (-90.12, f"90{LABEL_WEST}"), + (0.123, f"0{LABEL_DEGREE}"), + (90.1234, f"90{LABEL_EAST}"), + (180.12345, f"180{LABEL_DEGREE}"), ], ) def test_truncation(value, expected): diff --git a/tests/gridlines/test_create_meridians.py b/tests/gridlines/test_create_meridians.py index 242cdbca..b7ca17ad 100644 --- a/tests/gridlines/test_create_meridians.py +++ b/tests/gridlines/test_create_meridians.py @@ -64,7 +64,7 @@ def test_core(n_samples, zlevel, step): blocks_meridians = deindex(result.blocks.keys()) assert blocks_meridians == meridians # check the meridian meshes (blocks) - for key in result.blocks.keys(): + for key in result.blocks.keys(): # noqa: SIM118 mesh = result.blocks[key] assert mesh.n_points == n_samples assert mesh.n_cells == (n_lines := n_samples - 1) @@ -129,7 +129,7 @@ def test_closed_interval(): idxs = np.where(np.array(blocks_meridians) == boundary)[0] assert idxs.size == 2 remesh_found = 0 - for key in result.blocks.keys(): + for key in result.blocks.keys(): # noqa: SIM118 mesh = result.blocks[key] if deindex(key) == boundary: if GV_REMESH_POINT_IDS in mesh.point_data: diff --git a/tests/gridlines/test_create_parallel_labels.py b/tests/gridlines/test_create_parallel_labels.py index dc6451de..b1e641de 100644 --- a/tests/gridlines/test_create_parallel_labels.py +++ b/tests/gridlines/test_create_parallel_labels.py @@ -18,18 +18,18 @@ def test_empty(): @pytest.mark.parametrize( - "value, expected", + ("value", "expected"), [ - [-90, f"90{LABEL_SOUTH}"], - [[-90], f"90{LABEL_SOUTH}"], - [-45, f"45{LABEL_SOUTH}"], - [[-45], f"45{LABEL_SOUTH}"], - [0, f"0{LABEL_DEGREE}"], - [[0], f"0{LABEL_DEGREE}"], - [45, f"45{LABEL_NORTH}"], - [[45], f"45{LABEL_NORTH}"], - [90, f"90{LABEL_NORTH}"], - [[90], f"90{LABEL_NORTH}"], + (-90, f"90{LABEL_SOUTH}"), + ([-90], f"90{LABEL_SOUTH}"), + (-45, f"45{LABEL_SOUTH}"), + ([-45], f"45{LABEL_SOUTH}"), + (0, f"0{LABEL_DEGREE}"), + ([0], f"0{LABEL_DEGREE}"), + (45, f"45{LABEL_NORTH}"), + ([45], f"45{LABEL_NORTH}"), + (90, f"90{LABEL_NORTH}"), + ([90], f"90{LABEL_NORTH}"), ], ) def test(value, expected): @@ -39,13 +39,13 @@ def test(value, expected): @pytest.mark.parametrize( - "value, expected", + ("value", "expected"), [ - [-90, None], - [-45, f"45{LABEL_SOUTH}"], - [0, f"0{LABEL_DEGREE}"], - [45, f"45{LABEL_NORTH}"], - [90, None], + (-90, None), + (-45, f"45{LABEL_SOUTH}"), + (0, f"0{LABEL_DEGREE}"), + (45, f"45{LABEL_NORTH}"), + (90, None), ], ) def test_default_poles_parallel(value, expected): @@ -56,13 +56,13 @@ def test_default_poles_parallel(value, expected): @pytest.mark.parametrize( - "value, expected", + ("value", "expected"), [ - [-90.1, f"90{LABEL_SOUTH}"], - [-45.12, f"45{LABEL_SOUTH}"], - [0.123, f"0{LABEL_DEGREE}"], - [45.1234, f"45{LABEL_NORTH}"], - [90.12345, f"90{LABEL_NORTH}"], + (-90.1, f"90{LABEL_SOUTH}"), + (-45.12, f"45{LABEL_SOUTH}"), + (0.123, f"0{LABEL_DEGREE}"), + (45.1234, f"45{LABEL_NORTH}"), + (90.12345, f"90{LABEL_NORTH}"), ], ) def test_truncation(value, expected): diff --git a/tests/gridlines/test_create_parallels.py b/tests/gridlines/test_create_parallels.py index 0131628d..a0dcc8dc 100644 --- a/tests/gridlines/test_create_parallels.py +++ b/tests/gridlines/test_create_parallels.py @@ -63,7 +63,7 @@ def test_core(n_samples, zlevel, poles_label): blocks_parallels = deindex(result.blocks.keys()) assert blocks_parallels == parallels # check the parallel meshes (blocks) - for key in result.blocks.keys(): + for key in result.blocks.keys(): # noqa: SIM118 mesh = result.blocks[key] assert mesh.n_points == n_samples assert mesh.n_cells == n_samples @@ -135,7 +135,7 @@ def test_poles_parallel(): blocks_parallels = deindex(result.blocks.keys()) assert blocks_parallels == parallels # check the parallel meshes (blocks) - for key in result.blocks.keys(): + for key in result.blocks.keys(): # noqa: SIM118 mesh = result.blocks[key] assert mesh.n_points == LATITUDE_N_SAMPLES assert mesh.n_cells == LATITUDE_N_SAMPLES diff --git a/tests/plotting/test_examples.py b/tests/plotting/test_examples.py index 839ab108..f88590e4 100644 --- a/tests/plotting/test_examples.py +++ b/tests/plotting/test_examples.py @@ -52,7 +52,7 @@ } -@pytest.mark.image +@pytest.mark.image() @pytest.mark.parametrize("script", SCRIPTS) def test(script, verify_image_cache): """Image test the example scripts.""" diff --git a/tests/search/conftest.py b/tests/search/conftest.py index 67741150..1063fa4a 100644 --- a/tests/search/conftest.py +++ b/tests/search/conftest.py @@ -1,14 +1,42 @@ """pytest fixture infra-structure for :mod:`geovista.search` unit-tests.""" from __future__ import annotations -from collections import namedtuple +from typing import TYPE_CHECKING, NamedTuple import pytest -Center = namedtuple("Center", ["cid", "pids"]) -Neighbour = namedtuple("Neighbour", ["cid", "expected"]) -POI = namedtuple("POI", ["name", "lon", "lat", "cid"]) -Vertex = namedtuple("Vertex", ["pid", "cids"]) +if TYPE_CHECKING: + from collections.abc import Iterable + + +class Center(NamedTuple): + """Define cell and associated vertices.""" + + cid: int + pids: Iterable[int] + + +class Neighbour(NamedTuple): + """Define cell and associated neighbouring cells.""" + + cid: int + expected: Iterable[int] + + +class POI(NamedTuple): + """Define cell and named geolocated point-of-interest.""" + + name: str + lon: float + lat: float + cid: int + + +class Vertex(NamedTuple): + """Define vertex and parent cell/s.""" + + pid: int + cids: Iterable[int] @pytest.fixture( diff --git a/tests/search/test_Preference.py b/tests/search/test_Preference.py index 124cc6ae..d9230aa9 100644 --- a/tests/search/test_Preference.py +++ b/tests/search/test_Preference.py @@ -24,7 +24,7 @@ def test_values(): @pytest.mark.parametrize( - "member, expected", + ("member", "expected"), [ ("center", True), ("Center", True), diff --git a/tests/search/test_find_nearest_cell.py b/tests/search/test_find_nearest_cell.py index 42679470..99475a9f 100644 --- a/tests/search/test_find_nearest_cell.py +++ b/tests/search/test_find_nearest_cell.py @@ -23,8 +23,8 @@ def test_cell_centers(lam_uk): @pytest.mark.xfail(reason="requires projection support") def test_cell_centers__lam_rotated_pole(): """Test requires projection support before implementing.""" - # TODO: complete this test case - raise AssertionError() + # TODO @bjlittle: Complete this test case. + raise AssertionError def test_poi(lam_uk, poi): diff --git a/tests/transform/test_transform_points.py b/tests/transform/test_transform_points.py index 740b4a56..94be8c48 100644 --- a/tests/transform/test_transform_points.py +++ b/tests/transform/test_transform_points.py @@ -10,7 +10,7 @@ @pytest.mark.parametrize( - "src_crs, tgt_crs", [(None, WGS84), (WGS84, None), (None, None)] + ("src_crs", "tgt_crs"), [(None, WGS84), (WGS84, None), (None, None)] ) def test_crs_fail(src_crs, tgt_crs): """Test trap of invalid source and/or target CRSs.""" @@ -21,7 +21,7 @@ def test_crs_fail(src_crs, tgt_crs): @pytest.mark.parametrize( - "xbad, ybad", + ("xbad", "ybad"), [ (True, False), (False, True), @@ -44,7 +44,7 @@ def test_xy_dimension_fail(xbad, ybad): @pytest.mark.parametrize( - "xs, ys", + ("xs", "ys"), [(np.arange(10), np.arange(20)), (np.arange(10), np.arange(20).reshape(10, 2))], ) def test_xy_size_fail(xs, ys):