Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,15 @@ resolution_z = 25.0 # metres between samples
crs = 'EPSG:2193' # CRS the profiles are extracted in

[[grid.sites]]
name = "CACS"
longitude = 172.6218
latitude = -43.5283
site = "CACS" # not a keyword; see below
network = "NZ"
```

Sites come in a global CRS (WGS84 unless `sites_crs` says otherwise), and the
builder maps them into `grid.projection.crs`. Instead of listing them inline,
point `sites` at a CSV or Parquet file with `name`, `longitude` and `latitude`
point `sites` at a CSV or Parquet file with `longitude` and `latitude`
columns:

```toml
Expand All @@ -181,10 +182,37 @@ sites = "stations.csv"
Keep that line ahead of `[grid.projection]`: TOML would otherwise read it as
a key of that table.

#### Site labels

Longitude and latitude place a site, and the config reserves nothing else.
Every other key, and every other column of a site file, becomes a coordinate
on the grid's `i` axis under the name the caller gave it, and a column in
table output. Neither `site` nor `network` in the preceding example is a
keyword the grid interprets. Both end up in the output because nothing
reserves them. Rename them, add a driller's reference, drop them entirely: the
grid doesn't care.

A label keeps the type the caller wrote, so a numeric column arrives numeric.
Each site needs the same set of labels, since the alternative is a column of
nulls where one site was missing a key.

`nzcvm.grids.grid.RESERVED_COORDINATES` lists the names a label may not take:
the variables and attributes `GridSchema` declares (`x`, `y`, `z`, `depth`,
`name`, `geometry`, …), the `(i, j, k)` index, and the components a writer
puts alongside the grid. A coordinate shadows a variable of the same name, so
a label called `name` would turn `grid.name` from the grid's name into an
array. Naming one of those raises rather than corrupting the grid.

To drop the labels and keep only the spatial coordinates:

```toml
keep_extra_columns = false
```

The result has shape `(len(sites), 1, nk)`: one column per site, with `nk`
samples down each column. The singleton `j` axis preserves the `(i, j, k)`
contract every layer relies on. A `site` coordinate labels the `i` axis, so
the output reads back per station:
contract every layer relies on. The site labels index the `i` axis, so the
output reads back per station:

```python
import xarray as xr
Expand All @@ -205,9 +233,9 @@ uv run nzcvm generate examples/borehole.toml boreholes.csv
```

```
grid,site,i,j,k,x,y,z,depth,rho,vp,vs,qp,qs,alpha
boreholes,GULL,0,0,0,1531509.5,5161095.5,-641.124146,0,1810,1800.00012,500,100,50,1
boreholes,GULL,0,0,1,1531509.5,5161095.5,-616.124146,25,1810,1800,500,100,50,1
grid,site,network,i,j,k,x,y,z,depth,rho,vp,vs,qp,qs,alpha
boreholes,GULL,NZ,0,0,0,1531509.5,5161095.5,-641.124146,0,1810,1800.00012,500,100,50,1
boreholes,GULL,NZ,0,0,1,1531509.5,5161095.5,-616.124146,25,1810,1800,500,100,50,1
```

which `pandas` groups straight back into profiles:
Expand Down Expand Up @@ -607,7 +635,9 @@ up again. The borehole grid labels its columns this way:
grid = grid.assign_coords(site=("i", ["GULL", "TERR"]))
```

The `csv` and `parquet` writers turn any such coordinate into a label column.
The name must avoid `RESERVED_COORDINATES`, since a coordinate shadows a
variable or attribute of the same name. The `csv` and `parquet` writers turn
any coordinate outside the contract into a label column.

Here is a transect: a line of vertical columns between two
points, shaped `(n, 1, nk)`.
Expand Down
24 changes: 17 additions & 7 deletions examples/borehole.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
# A borehole grid extracts one vertical profile per site instead of filling a
# volume, so it has no extent, no azimuth and no model origin: just a
# projection to extract the profiles in.
#
# Longitude and latitude place a site. Every other key below (`site`,
# `network`) is one the grid knows nothing about, and rides through to the
# output as a coordinate on the i axis. Set keep_extra_columns = false to drop
# them.

[metadata]
title = "Synthetic borehole profiles"
Expand All @@ -20,34 +25,39 @@ surface = "./synthetic/dem.zarr"
depth = 600.0
resolution_z = 25.0

# Sites can also come from a CSV or Parquet file with name, longitude and
# latitude columns. It has to stay above the tables below, as TOML would
# otherwise read it as a key of `[grid.projection]`.
# Sites can also come from a CSV or Parquet file with longitude and latitude
# columns, where the other columns label the sites the same way. It has to
# stay above the tables below, as TOML would otherwise read it as a key of
# `[grid.projection]`.
#
# sites = "examples/sites.csv"

[grid.projection]
crs = 'EPSG:2193'

[[grid.sites]]
name = "GULL" # inside the `gully` basin
longitude = 172.15
latitude = -43.70
site = "GULL" # inside the `gully` basin
network = "NZ"

[[grid.sites]]
name = "TERR" # inside the `terrace` basin
longitude = 172.30
latitude = -43.53
site = "TERR" # inside the `terrace` basin
network = "NZ"

[[grid.sites]]
name = "RIDG" # up in the hills, outside every basin
longitude = 172.10
latitude = -43.45
site = "RIDG" # up in the hills, outside every basin
network = "SC"

[[grid.sites]]
name = "SEAB" # offshore
longitude = 172.55
latitude = -43.60
site = "SEAB" # offshore
network = "SC"

[[layers]]
type = "clamp"
Expand Down
10 changes: 5 additions & 5 deletions examples/sites.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name,longitude,latitude
GULL,172.15,-43.70
TERR,172.30,-43.53
RIDG,172.10,-43.45
SEAB,172.55,-43.60
site,network,longitude,latitude
GULL,NZ,172.15,-43.70
TERR,NZ,172.30,-43.53
RIDG,SC,172.10,-43.45
SEAB,SC,172.55,-43.60
59 changes: 48 additions & 11 deletions nzcvm/config/grids/borehole.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dataclasses import dataclass, field
from pathlib import Path
from typing import Literal
from typing import Any, Literal

from mashumaro import field_options
from pyproj import CRS
Expand All @@ -12,7 +12,6 @@
GeographicCRS,
Latitude,
Longitude,
NonEmptyStr,
PositiveFloat,
)
from nzcvm.coordinates import WGS84_EPSG, Coordinate
Expand All @@ -22,23 +21,51 @@
DEFAULT_CHUNK_SIZES = {Coordinate.I: 64}


#: The keys that place a site. Everything else given for a site is a label.
SPATIAL_KEYS = ("longitude", "latitude")


@dataclass
class Site(ConfigObject):
"""One borehole location, in the global CRS.
"""One borehole location, in the global CRS, plus whatever labels it has.

Longitude and latitude place the site, and the config doesn't reserve any
other key, so a site takes as much or as little description as the caller
has to give it. Each label becomes a coordinate on the grid's ``i`` axis
and a column in table output.

Attributes
----------
name :
Label for the site. The builder keeps it on the ``site`` coordinate
of the grid, so the output reads back per station.
longitude, latitude :
Position in :attr:`BoreholeGridConfig.sites_crs`, which defaults to
WGS84. The grid builder projects it into the grid CRS.
labels :
Everything else given for the site. Decoding a config folds every
key except :data:`SPATIAL_KEYS` in here, so a config file needn't
spell the mapping out.

Examples
--------
>>> Site.from_dict(
... {"longitude": 172.15, "latitude": -43.7, "site": "GULL", "network": "NZ"}
... )
Site(longitude=172.15, latitude=-43.7, labels={'site': 'GULL', 'network': 'NZ'})
"""

name: NonEmptyStr
longitude: Longitude
latitude: Latitude
labels: dict[str, Any] = field(default_factory=dict)

@classmethod
def __pre_deserialize__(cls, d: dict[str, Any]) -> dict[str, Any]:
"""Fold every key that doesn't place the site into :attr:`labels`.

Named *d* to match the mashumaro hook this overrides.
"""
return {
**{k: v for k, v in d.items() if k in SPATIAL_KEYS},
"labels": {k: v for k, v in d.items() if k not in SPATIAL_KEYS},
}


@dataclass
Expand All @@ -62,7 +89,8 @@ class BoreholeGridConfig(GridConfig):
to elevation, so each column starts at the ground.
sites :
Either an inline list of :class:`Site` objects, or a path to a CSV or
Parquet file with ``name``, ``longitude`` and ``latitude`` columns.
Parquet file with ``longitude`` and ``latitude`` columns. Any other
key or column labels the site.
depth :
Depth of the bottom of every column, in metres below the topography.
resolution_z :
Expand All @@ -73,10 +101,16 @@ class BoreholeGridConfig(GridConfig):
Geographic CRS of the site coordinates (default WGS84). The builder
maps each site from here into *projection* before querying. It has to
be geographic, since a site is a longitude and a latitude.
keep_extra_columns :
Whether the builder puts the site labels on the grid, and so in the
output (default ``True``). Set it to ``False`` to keep only the
spatial coordinates and drop the rest.

Examples
--------
TOML, with the sites inline::
TOML, with the sites inline. Neither ``site`` nor ``network`` is a
keyword here, and both end up in the output because nothing reserves
them::

[grid]
type = "borehole"
Expand All @@ -88,11 +122,12 @@ class BoreholeGridConfig(GridConfig):
crs = 'EPSG:2193'

[[grid.sites]]
name = "CACS"
longitude = 172.62
latitude = -43.53
site = "CACS"
network = "NZ"

or read from a file::
or read from a file, where the extra columns do the same job::

sites = "examples/sites.csv"
"""
Expand All @@ -111,6 +146,8 @@ class BoreholeGridConfig(GridConfig):
metadata=field_options(serialization_strategy=CRSStrategy()),
)

keep_extra_columns: bool = True

chunks: dict[Coordinate, int] = field(default_factory=lambda: DEFAULT_CHUNK_SIZES)

type: Literal["borehole"] = "borehole"
Expand Down
1 change: 0 additions & 1 deletion nzcvm/coordinates.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ class Coordinate(StrEnum):
Z = auto()
DEPTH = auto()
COASTLINE = auto()
SITE = auto()
I = auto()
J = auto()
K = auto()
Expand Down
Loading
Loading