-
Notifications
You must be signed in to change notification settings - Fork 22
Improve global stereographic meshing with configurable k0 and CRS support #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -428,6 +428,67 @@ points, cells = om.generate_multiscale_mesh([sdf1, sdf2], [el1, el2]) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Global meshes are defined in WGS84 but meshed in a stereographic projection. Regional refinement can be added as additional domains. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #### Understanding Stereographic Projections for Global Meshes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Global ocean meshes require special handling due to the distortion | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inherent in projecting a sphere onto a 2D plane. OceanMesh uses a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **north-polar stereographic projection** for global meshing, which: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Preserves angles (conformal projection) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Minimises distortion near the poles | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Allows efficient 2D mesh generation algorithms to work on projected | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| coordinates | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **Cartopy Integration (Optional Dependency)** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| For accurate stereographic projections, OceanMesh can use | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [cartopy](https://scitools.org.uk/cartopy/docs/latest/)'s | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``NorthPolarStereo`` coordinate reference system. Install cartopy for | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| global meshing via the ``global`` extra: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pip install oceanmesh[global] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| or manually: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pip install cartopy | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| If cartopy is not installed, OceanMesh falls back to legacy analytic | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| formulas (less accurate but functional). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **Scale Factor (k0) Configuration** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| The stereographic projection uses a **scale factor** :math:`k_0` that | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| controls distortion at different latitudes. The local scale factor at | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| latitude :math:`\phi` is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .. math:: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| k(\phi) = \frac{2 k_0}{1 + \sin \phi}. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Common :math:`k_0` values: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - :math:`k_0 = 1.0` (default): standard stereographic with true scale | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| at the pole. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - :math:`k_0 = 0.994`: used in EPSG:3413 (Arctic) and EPSG:3031 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (Antarctic) to minimise distortion at standard parallels (~70°N/S). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| The scale factor affects mesh sizing: smaller :math:`k_0` reduces | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| element sizes near the poles, larger :math:`k_0` increases them. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| The stereographic projection uses a **scale factor** :math:`k_0` that | |
| controls distortion at different latitudes. The local scale factor at | |
| latitude :math:`\phi` is | |
| .. math:: | |
| k(\phi) = \frac{2 k_0}{1 + \sin \phi}. | |
| Common :math:`k_0` values: | |
| - :math:`k_0 = 1.0` (default): standard stereographic with true scale | |
| at the pole. | |
| - :math:`k_0 = 0.994`: used in EPSG:3413 (Arctic) and EPSG:3031 | |
| (Antarctic) to minimise distortion at standard parallels (~70°N/S). | |
| The scale factor affects mesh sizing: smaller :math:`k_0` reduces | |
| element sizes near the poles, larger :math:`k_0` increases them. | |
| The stereographic projection uses a **scale factor** $k_0$ that | |
| controls distortion at different latitudes. The local scale factor at | |
| latitude $\\phi$ is | |
| .. math:: | |
| k(\phi) = \frac{2 k_0}{1 + \sin \phi}. | |
| Common $k_0$ values: | |
| - $k_0 = 1.0$ (default): standard stereographic with true scale | |
| at the pole. | |
| - $k_0 = 0.994$: used in EPSG:3413 (Arctic) and EPSG:3031 | |
| (Antarctic) to minimise distortion at standard parallels (~70°N/S). | |
| The scale factor affects mesh sizing: smaller $k_0$ reduces | |
| element sizes near the poles, larger $k_0$ increases them. |
Outdated
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reStructuredText-style inline math notation (:math:) won't render properly in a Markdown file. Use standard Markdown math notation with $ delimiters instead, e.g., $k_0 = 1.0$ for inline math.
| The stereographic projection uses a **scale factor** :math:`k_0` that | |
| controls distortion at different latitudes. The local scale factor at | |
| latitude :math:`\phi` is | |
| .. math:: | |
| k(\phi) = \frac{2 k_0}{1 + \sin \phi}. | |
| Common :math:`k_0` values: | |
| - :math:`k_0 = 1.0` (default): standard stereographic with true scale | |
| at the pole. | |
| - :math:`k_0 = 0.994`: used in EPSG:3413 (Arctic) and EPSG:3031 | |
| (Antarctic) to minimise distortion at standard parallels (~70°N/S). | |
| The scale factor affects mesh sizing: smaller :math:`k_0` reduces | |
| element sizes near the poles, larger :math:`k_0` increases them. | |
| The stereographic projection uses a **scale factor** $k_0$ that | |
| controls distortion at different latitudes. The local scale factor at | |
| latitude $\phi$ is | |
| $$k(\phi) = \frac{2 k_0}{1 + \sin \phi}.$$ | |
| Common $k_0$ values: | |
| - $k_0 = 1.0$ (default): standard stereographic with true scale | |
| at the pole. | |
| - $k_0 = 0.994$: used in EPSG:3413 (Arctic) and EPSG:3031 | |
| (Antarctic) to minimise distortion at standard parallels (~70°N/S). | |
| The scale factor affects mesh sizing: smaller $k_0$ reduces | |
| element sizes near the poles, larger $k_0$ increases them. |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -126,6 +126,11 @@ | |||||
|
|
||||||
| __version__ = _version.get_versions()["version"] | ||||||
|
|
||||||
| from . import _version | ||||||
| try: # Optional global-stereo helpers (import may fail generically) | ||||||
| from .projections import CARTOPY_AVAILABLE | ||||||
|
||||||
| from .projections import CARTOPY_AVAILABLE | |
| from .projections import CARTOPY_AVAILABLE, StereoProjection |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -88,7 +88,9 @@ def enforce_mesh_size_bounds_elevation(grid, dem, bounds): | |
| return grid | ||
|
|
||
|
|
||
| def enforce_mesh_gradation(grid, gradation=0.15, crs="EPSG:4326", stereo=False): | ||
| def enforce_mesh_gradation( | ||
| grid, gradation=0.15, crs="EPSG:4326", stereo=False, scale_factor=1.0 | ||
| ): | ||
| """Enforce a mesh size gradation bound `gradation` on a :class:`grid` | ||
|
|
||
| Parameters | ||
|
|
@@ -124,28 +126,78 @@ def enforce_mesh_gradation(grid, gradation=0.15, crs="EPSG:4326", stereo=False): | |
| tmp = gradient_limit([*sz], elen, gradation, 10000, cell_size) | ||
| tmp = np.reshape(tmp, (sz[0], sz[1]), "F") | ||
| if stereo: | ||
| logger.info("Global mesh: fixing gradient on the north pole...") | ||
| # max distortion at the pole: 2 / 180 * PI / (1 - cos(lat))**2 | ||
| dx_stereo = grid.dx * 1 / 180 * np.pi / 2 | ||
| # in stereo projection, all north hemisphere is contained in the unit sphere | ||
| # we want to fix the gradient close to the north pole, | ||
| # so we extract all the coordinates between -1 and 1 in stereographic projection | ||
| from .projections import CARTOPY_AVAILABLE | ||
| from .region import _get_stereo_projection | ||
|
|
||
| backend = "cartopy" if CARTOPY_AVAILABLE else "hardcoded" | ||
| logger.info( | ||
| "Global mesh: fixing gradient on the north pole (backend=%s, k0=%s, distortion_correction=%s)...", | ||
| backend, | ||
| scale_factor, | ||
| "enabled" if CARTOPY_AVAILABLE else "disabled", | ||
| ) | ||
|
|
||
| proj = _get_stereo_projection(scale_factor=scale_factor) | ||
|
|
||
| # Choose a reasonable physical radius (in metres) around the | ||
| # north pole within which to regularise the gradient. Here we | ||
| # use ~2000 km. | ||
| r_max = 2_000_000.0 | ||
|
||
|
|
||
| # Resolution in stereographic metres, based on underlying lon/lat step. | ||
| dx_deg = grid.dx | ||
| dx_stereo = dx_deg * np.pi / 180.0 * 6_371_000.0 | ||
|
||
|
|
||
| us, vs = np.meshgrid( | ||
| np.arange(-1, 1, dx_stereo), np.arange(-1, 1, dx_stereo), indexing="ij" | ||
| np.arange(-r_max, r_max, dx_stereo), | ||
| np.arange(-r_max, r_max, dx_stereo), | ||
| indexing="ij", | ||
| ) | ||
| ulon, vlat = to_lat_lon(us.ravel(), vs.ravel()) | ||
|
|
||
| if proj is not None: | ||
| ulon, vlat = proj.to_lat_lon(us.ravel(), vs.ravel()) | ||
| # Spatially-varying stereographic scale factor k(phi) on | ||
| # the intermediate polar grid. | ||
| k_polar = proj.get_scale_factor(vlat) | ||
| else: | ||
| # Fall back to module-level transform if projection could | ||
| # not be constructed. Normalise metre-based coordinates by | ||
| # Earth radius so that to_lat_lon operates on the | ||
| # non-dimensional unit-sphere coordinates expected by the | ||
| # legacy stereographic formulas used when CARTOPY_AVAILABLE | ||
| # is False. This keeps the polar patch consistent with | ||
| # to_stereo/to_lat_lon. | ||
| R_earth = 6_371_000.0 | ||
| us_unit = us / R_earth | ||
| vs_unit = vs / R_earth | ||
| ulon, vlat = to_lat_lon(us_unit.ravel(), vs_unit.ravel()) | ||
| k_polar = None | ||
|
|
||
| utmp = grid.eval((ulon, vlat)) | ||
| utmp = np.reshape(utmp, us.shape) | ||
| szs = utmp.shape | ||
|
|
||
| # Distortion-aware gradient limiting: operate on mesh sizes | ||
| # scaled by the local stereographic factor so the limiter | ||
| # acts in approximately physical space. | ||
| if k_polar is not None: | ||
| k_polar_grid = np.reshape(k_polar, utmp.shape) | ||
| utmp_work = utmp * k_polar_grid | ||
| else: | ||
| utmp_work = utmp | ||
|
|
||
| szs = utmp_work.shape | ||
| szs = (szs[0], szs[1], 1) | ||
| # we choose an excessively large number for the gradiation = 10 | ||
| # this is merely to fix the north pole gradient | ||
| vtmp = gradient_limit([*szs], dx_stereo, 10, 10000, utmp.flatten("F")) | ||
| vtmp = gradient_limit([*szs], dx_stereo, 10, 10000, utmp_work.flatten("F")) | ||
| vtmp = np.reshape(vtmp, (szs[0], szs[1]), "F") | ||
| # construct stereo interpolating function | ||
|
|
||
| # Inverse correction: return to geographic sizing by dividing | ||
| # out the stereographic distortion. | ||
| if k_polar is not None: | ||
| vtmp /= k_polar_grid | ||
|
|
||
| # construct stereo interpolating function in projection metres | ||
| grid_stereo = Grid( | ||
| bbox=(-1, 1, -1, 1), | ||
| bbox=(-r_max, r_max, -r_max, r_max), | ||
| dx=dx_stereo, | ||
| values=vtmp, | ||
| hmin=grid.hmin, | ||
|
|
@@ -155,7 +207,11 @@ def enforce_mesh_gradation(grid, gradation=0.15, crs="EPSG:4326", stereo=False): | |
| grid_stereo.build_interpolant() | ||
| # reinject back into the original grid and redo the gradient computation | ||
| xg, yg = grid.create_grid() | ||
| tmp[yg > 0] = grid_stereo.eval(to_stereo(xg[yg > 0], yg[yg > 0])) | ||
| if proj is not None: | ||
| xs, ys = proj.to_stereo(xg[yg > 0], yg[yg > 0]) | ||
| else: | ||
| xs, ys = to_stereo(xg[yg > 0], yg[yg > 0]) | ||
| tmp[yg > 0] = grid_stereo.eval((xs, ys)) | ||
| logger.info( | ||
| "Global mesh: reinject back stereographic gradient and recomputing gradient..." | ||
| ) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The math notation mixes inline LaTeX (
:math:) with block-level reStructuredText directive (.. math::). In a Markdown file, these reStructuredText constructs won't render properly. Consider using standard Markdown math notation with$for inline math and$$for display math, or moving this documentation to a.rstfile if Sphinx/reStructuredText formatting is required.