Skip to content
Merged
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
10 changes: 8 additions & 2 deletions js/store-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const PRESETS = [
// gridlook's keys (url, varname, projection, lat, lon, dimIndices_<dim>) and
// this page's own, which gridlook ignores
const defaults = { url: PRESETS[0].url, varname: "", projection: "mollweide", lat: null, lon: null,
t: 0, colour: "data", chunks: 1, cells: 1, coast: 1, grat: 1, pick: "", cam: null };
t: 0, colour: "data", chunks: 1, cells: 1, coast: 1, grat: 1, pick: "", cam: null, assume: "" };
const state = { ...defaults, dims: {} };

function readHash() {
Expand All @@ -74,6 +74,7 @@ function readHash() {
const c = params.get("colour");
if (["data", "area", "omega", "none"].includes(c)) state.colour = c;
state.pick = params.get("pick") || "";
state.assume = R.ASSUME[params.get("assume")] ? params.get("assume") : "";
const cam = (params.get("cam") || "").split(",").map(Number);
state.cam = cam.length === 6 && cam.every(isFinite) ? cam : null;
for (const [k, v] of params) if (k.startsWith("dimIndices_") && isFinite(+v)) state.dims[k.slice(11)] = Math.max(0, Math.round(+v));
Expand All @@ -90,6 +91,7 @@ function writeHash() {
if (state.t) p.t = +state.t.toFixed(3);
for (const k of ["colour", "chunks", "cells", "coast", "grat"]) if (state[k] !== defaults[k]) p[k] = state[k];
if (state.pick) p.pick = state.pick;
if (state.assume) p.assume = state.assume;
if (state.cam) p.cam = state.cam.map((x) => +x.toFixed(2)).join(",");
history.replaceState(null, "", "#" + C.hashString(state.url, p));
$("open-full").href = location.pathname + location.hash;
Expand Down Expand Up @@ -727,7 +729,10 @@ async function open() {
if (!state.varname) throw new Error("the store lists no arrays: add ::varname=<array> to the address");
if (![...sel.options].some((o) => o.value === state.varname)) { const o = document.createElement("option"); o.value = o.textContent = state.varname; sel.appendChild(o); }
sel.value = state.varname;
const v = await R.describeVariable(s, state.varname);
const v = await R.describeVariable(s, state.varname, { assume: state.assume });
// the choice of satellite, offered only where the store leaves it open
$("assume-row").hidden = !(v.abiLike || (v.crs && v.crs.assumed));
$("assume").value = state.assume;
const F = v.grid ? await crsOf(v) : null;
if (serial !== openSerial) return;
const g = v.grid || { x0: 0, dx: 1, y0: 0, dy: 1, nx: v.meta.shape[v.ix], ny: v.meta.shape[v.iy] };
Expand Down Expand Up @@ -1010,6 +1015,7 @@ $("centre-grid").addEventListener("click", () => {
if (cur) { buildFlat(); place(); }
writeHash();
});
$("assume").addEventListener("change", (e) => { state.assume = e.target.value; writeHash(); open(); });
ui.colour.addEventListener("change", () => { state.colour = ui.colour.value; if (cur) { drawTexture(); showLegend(); } writeHash(); });
for (const k of ["chunks", "cells", "coast", "grat"])
$(k).addEventListener("change", () => { state[k] = $(k).checked ? 1 : 0; if (cur) { drawTexture(); place(); showLegend(); } writeHash(); });
Expand Down
29 changes: 24 additions & 5 deletions js/store-read.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,15 @@ async function bodyNamed(s, attrs, dir) {
// Where the cells of the variable at path are, and in which CRS:
// { path, meta, dims, ix, iy, grid (storecrs.gridOf), crs (a cfProj()-like
// description or null), crsFrom, notes[] }.
export async function describeVariable(s, path) {
// Satellites whose fixed grid can be assumed when a store has GOES ABI scan
// angles but names no satellite (the ABI fixed grid's own parameters).
export const ASSUME = {
goes_east: { label: "GOES-East (75.0 W)", lon0: -75 },
goes_west: { label: "GOES-West (137.0 W)", lon0: -137 }
};
const ABI_H = 35786023;

export async function describeVariable(s, path, opts = {}) {
const meta = await nodeMeta(s, path);
if (!meta || meta.group) throw new Error(`${path || "the root"} is not an array`);
const nd = meta.shape.length;
Expand Down Expand Up @@ -275,19 +283,30 @@ export async function describeVariable(s, path) {
}

// without a transform, the coordinate arrays
let x = null, y = null, xs = 1, ys = 1, xc = null, yc = null;
let x = null, y = null, xs = 1, ys = 1, xc = null, yc = null, abiLike = false;
if (!t && !curv) {
const cx = coords[ix] ? await read1d(s, join(dir, dims[ix])) : null;
const cy = coords[iy] ? await read1d(s, join(dir, dims[iy])) : null;
if (cx && cy) {
const lonlat = role[ix] === "lon" && role[iy] === "lat";
x = axisOf(cx.values, { lon: lonlat || role[ix] === "rlon" });
y = axisOf(cy.values);
// scan angles with no CRS: a GOES ABI fixed grid, if the user says whose
const u = String(cx.attrs.units || "").toLowerCase();
abiLike = !crs && !lonlat && x.regular && y.regular && (!u || u.startsWith("rad")) &&
Math.max(...[cx.values[0], cx.values[cx.values.length - 1], cy.values[0], cy.values[cy.values.length - 1]].map((q) => Math.abs(Number(q)))) <= 0.16;
if (abiLike && ASSUME[opts.assume]) {
const a = ASSUME[opts.assume];
crs = { proj4: `+proj=geos +h=${ABI_H} +lon_0=${a.lon0} +sweep=x +x_0=0 +y_0=0 +a=6378137 +b=6356752.31414 +units=m +no_defs`, name: `${a.label} fixed grid, assumed`, h: ABI_H, assumed: opts.assume };
crsFrom = `an assumption: the store names no CRS, and its x and y look like the GOES ABI fixed grid, here taken as ${a.label}`;
} else if (abiLike) notes.push("x and y look like GOES ABI scan angles (radians), but nothing names the satellite: choose one under Assume to place the grid");
if (!lonlat && role[ix] !== "rlon") {
const h = crs && /\+proj=geos\b/.test(crs.def || crs.proj4 || "") && gma ? Number(gma.perspective_point_height) || 0 : 0;
const h = crs && crs.h ? crs.h : crs && /\+proj=geos\b/.test(crs.def || crs.proj4 || "") && gma ? Number(gma.perspective_point_height) || 0 : 0;
if (crs && crs.h && !cx.attrs.units) { xs = ys = h; } else {
xs = unitScale(cx.attrs.units, h); ys = unitScale(cy.attrs.units, h);
}
}
if (xs !== 1) notes.push(`x and y are in ${cx.attrs.units}: multiplied by ${xs} for the CRS, which is in metres`);
if (xs !== 1) notes.push(`x and y are in ${cx.attrs.units || "radians (assumed)"}: multiplied by ${xs} for the CRS, which is in metres`);
// uneven but 1D (a Gaussian grid's latitudes, say): kept as they are
if (!x.regular) xc = unwrapped(cx.values, lonlat || role[ix] === "rlon").map((v) => v * xs);
if (!y.regular) yc = Array.from(cy.values, (v) => v * ys);
Expand All @@ -313,7 +332,7 @@ export async function describeVariable(s, path) {
}
if (!grid) notes.push("nothing in the store says where the cells are: drawn in index space");
if (!crs && !curv) notes.push("no CRS found");
return { path, meta, dims, ix, iy, role, grid, crs, crsFrom, gm: gma, notes };
return { path, meta, dims, ix, iy, role, grid, crs, crsFrom, gm: gma, notes, abiLike };
}

// One chunk of the variable, as { data, shape } for the grid dimensions at
Expand Down
8 changes: 8 additions & 0 deletions store.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ <h1>playproj</h1>
<p id="describe" class="describe"></p>
<table id="facts" class="readout"></table>
<p id="notes" class="hint"></p>
<div id="assume-row" hidden>
<label class="row" for="assume"><span>Assume</span></label>
<select id="assume" class="wide" aria-label="Assume a satellite">
<option value="">Nothing: leave the grid unplaced</option>
<option value="goes_east">GOES-East fixed grid (75.0 W)</option>
<option value="goes_west">GOES-West fixed grid (137.0 W)</option>
</select>
</div>
</section>

<section>
Expand Down
7 changes: 6 additions & 1 deletion test/check-storecrs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@ for (const t of man.stores) {
let s, v;
try {
s = await R.openStore(`${base}/${t.name}`);
v = await R.describeVariable(s, t.variable);
if (t.assume) {
// left unplaced until a satellite is chosen
const v0 = await R.describeVariable(s, t.variable);
check(v0.abiLike && !v0.crs, `${t.name}: without an assumption, abiLike ${v0.abiLike}, crs ${JSON.stringify(v0.crs)}`);
}
v = await R.describeVariable(s, t.variable, { assume: t.assume });
} catch (e) { check(false, `${t.name}: ${e.message}`); continue; }
const g = v.grid;
check(s.kind === (t.name.endsWith(".icechunk") ? "icechunk" : t.kind ? "zarr2" : "zarr3"), `${t.name}: opened as ${s.kind}`);
Expand Down
Binary file added test/fixtures/store/abi.zarr/u_wind/c/0/0/0
Binary file not shown.
Binary file added test/fixtures/store/abi.zarr/u_wind/c/0/0/1
Binary file not shown.
Binary file added test/fixtures/store/abi.zarr/u_wind/c/0/1/0
Binary file not shown.
Binary file added test/fixtures/store/abi.zarr/u_wind/c/0/1/1
Binary file not shown.
51 changes: 51 additions & 0 deletions test/fixtures/store/abi.zarr/u_wind/zarr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"shape": [
1,
60,
60
],
"data_type": "float32",
"chunk_grid": {
"name": "regular",
"configuration": {
"chunk_shape": [
1,
30,
30
]
}
},
"chunk_key_encoding": {
"name": "default",
"configuration": {
"separator": "/"
}
},
"fill_value": "NaN",
"codecs": [
{
"name": "bytes",
"configuration": {
"endian": "little"
}
},
{
"name": "zstd",
"configuration": {
"level": 0,
"checksum": false
}
}
],
"attributes": {
"_FillValue": "AAAAAAAA+H8="
},
"dimension_names": [
"time",
"y",
"x"
],
"zarr_format": 3,
"node_type": "array",
"storage_transformers": []
}
Binary file added test/fixtures/store/abi.zarr/x/c/0
Binary file not shown.
45 changes: 45 additions & 0 deletions test/fixtures/store/abi.zarr/x/zarr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"shape": [
60
],
"data_type": "float64",
"chunk_grid": {
"name": "regular",
"configuration": {
"chunk_shape": [
60
]
}
},
"chunk_key_encoding": {
"name": "default",
"configuration": {
"separator": "/"
}
},
"fill_value": "NaN",
"codecs": [
{
"name": "bytes",
"configuration": {
"endian": "little"
}
},
{
"name": "zstd",
"configuration": {
"level": 0,
"checksum": false
}
}
],
"attributes": {
"_FillValue": "AAAAAAAA+H8="
},
"dimension_names": [
"x"
],
"zarr_format": 3,
"node_type": "array",
"storage_transformers": []
}
Binary file added test/fixtures/store/abi.zarr/y/c/0
Binary file not shown.
45 changes: 45 additions & 0 deletions test/fixtures/store/abi.zarr/y/zarr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"shape": [
60
],
"data_type": "float64",
"chunk_grid": {
"name": "regular",
"configuration": {
"chunk_shape": [
60
]
}
},
"chunk_key_encoding": {
"name": "default",
"configuration": {
"separator": "/"
}
},
"fill_value": "NaN",
"codecs": [
{
"name": "bytes",
"configuration": {
"endian": "little"
}
},
{
"name": "zstd",
"configuration": {
"level": 0,
"checksum": false
}
}
],
"attributes": {
"_FillValue": "AAAAAAAA+H8="
},
"dimension_names": [
"y"
],
"zarr_format": 3,
"node_type": "array",
"storage_transformers": []
}
Loading
Loading