File size: 8,135 Bytes
8240fca 8a28d42 8240fca 5ad4e5a 8240fca 5ad4e5a 8240fca 8a28d42 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | // Downloads of the selected sample: the bands exactly as stored (GeoTIFF bytes from the image file, not re-encoded).
//
// one band -> <cell>_<dataset>_<time>_<band>.tif
// all bands -> <cell>_<dataset>_<time>.zip: every band's .tif, the thumbnail, metadata.json (where it came from)
// Python -> a snippet that reads the same row group from Hugging Face (copied to the clipboard)
// VIIRS -> the tile's GeoTIFF as stored in its zip shard (already read); Python reads the same byte range
// AlphaEarth -> the shown image (.png); the shown band or all 64 at 10 m as a GeoTIFF written here; Python (rasterio)
import { zipSync } from 'https://cdn.jsdelivr.net/npm/fflate@0.8.2/+esm'
import { bandNames, readBands } from './bands.js'
import { imageUrl } from './samples.js'
const stem = (cell, name, sample) => [cell, name, sample.timestamp].filter(Boolean).join('_')
function save(blob, filename) {
const a = document.createElement('a')
a.href = URL.createObjectURL(blob); a.download = filename
document.body.append(a); a.click(); a.remove()
setTimeout(() => URL.revokeObjectURL(a.href), 10_000)
}
/** One band's GeoTIFF, as stored. */
export async function downloadBand(info, name, cell, sample, band) {
const [bytes, stats] = await readBands(info, sample, [band])
save(new Blob([bytes[band]], { type: 'image/tiff' }), `${stem(cell, name, sample)}_${band}.tif`)
return stats
}
/** Every band, the thumbnail and metadata.json, in one zip (stored, not recompressed: the GeoTIFFs are LZW). */
export async function downloadAll(info, name, cell, sample, thumbnailSrc, productId, stats) {
const bands = await bandNames(info, sample)
const [bytes] = await readBands(info, sample, bands, stats)
const base = stem(cell, name, sample), entries = {}
for (const b of bands) entries[`${base}_${b}.tif`] = bytes[b]
const thumb = new Uint8Array(await (await fetch(thumbnailSrc)).arrayBuffer())
const png = thumb[0] === 0x89 && thumb[1] === 0x50
entries[`${base}_thumbnail.${png ? 'png' : 'jpg'}`] = thumb
const meta = { grid_cell: cell, dataset: `Major-TOM/Core-${name}`, product_id: productId, timestamp: sample.timestamp ?? null,
cloud_cover: sample.cloud_cover ?? null, orbit_state: sample.orbit_state ?? null, epsg: sample.epsg,
centre_lat: sample.centre_lat, centre_lon: sample.centre_lon, source_file: imageUrl(info, sample.part),
row_group: sample.parquet_row, bands }
entries['metadata.json'] = new TextEncoder().encode(JSON.stringify(meta, null, 1) + '\n')
save(new Blob([zipSync(entries, { level: 0 })], { type: 'application/zip' }), `${base}.zip`)
return stats
}
/** Python that reads the same sample from Hugging Face (one row group of the image file). */
export function pythonSnippet(info, name, cell, sample, bands) {
return `import fsspec
import pyarrow.parquet as pq
url = "${imageUrl(info, sample.part)}"
with fsspec.open(url, "rb") as f: # reads only the parts of the file needed
row = pq.ParquetFile(f).read_row_group(${sample.parquet_row}, columns=${JSON.stringify(bands)})
# ${cell}, Major-TOM/Core-${name}: each band is a GeoTIFF, e.g.
# from rasterio.io import MemoryFile; MemoryFile(row["${bands[0]}"][0].as_py()).open().read(1)
`
}
/** A VIIRS tile's GeoTIFF, as stored in its zip shard (the bytes the panel already read). */
export function downloadViirs(cell, sample, bytes) {
save(new Blob([bytes], { type: 'image/tiff' }), `${cell}_VIIRS_${sample.year}.tif`)
}
/** Python that reads the same VIIRS tile: its byte range of the zip shard, opened as a GeoTIFF. */
export function viirsSnippet(info, cell, sample) {
return `import fsspec
from rasterio.io import MemoryFile
url = "${info.files}${sample.shard}"
with fsspec.open(url, "rb") as f: # one range request: the tile is stored uncompressed in the zip
f.seek(${sample.offset}); tile = f.read(${sample.size})
radiance = MemoryFile(tile).open().read(1) # ${cell}, ${sample.year}: nW/cm²/sr, 10 m, float32
`
}
// ---- AlphaEarth: GeoTIFFs written here (int8, uncompressed, one strip per band), the shown image, Python ---------
const TIFF_BYTES = { 2: 1, 3: 2, 4: 4, 12: 8 } // ASCII, SHORT, LONG, DOUBLE
/** int8 bands (north up, NODATA -128) as a GeoTIFF in a UTM zone: west and north edges in metres, square pixels. */
export function int8GeoTiff(bands, size, { epsg, west, north, res }) {
const n = bands.length, plane = size * size
const entries = [[256, 4, [size]], [257, 4, [size]], [258, 3, Array(n).fill(8)], [259, 3, [1]], [262, 3, [1]],
[273, 4, Array(n).fill(0)], [277, 3, [n]], [278, 4, [size]], [279, 4, Array(n).fill(plane)], [284, 3, [2]],
...(n > 1 ? [[338, 3, Array(n - 1).fill(0)]] : []), [339, 3, Array(n).fill(2)],
[33550, 12, [res, res, 0]], [33922, 12, [0, 0, 0, west, north, 0]],
[34735, 3, [1, 1, 0, 3, 1024, 0, 1, 1, 1025, 0, 1, 1, 3072, 0, 1, epsg]], // projected, pixel is area, EPSG
[42113, 2, [...'-128'].map(c => c.charCodeAt(0)).concat(0)]] // GDAL_NODATA
let at = 8 + 2 + 12 * entries.length + 4
const places = entries.map(([, type, values]) => {
const bytes = values.length * TIFF_BYTES[type]
if (bytes <= 4) return null
const here = at; at += bytes + (bytes % 2); return here
})
entries[5][2] = bands.map((_, b) => at + b * plane) // StripOffsets: after the values
const buffer = new ArrayBuffer(at + n * plane), view = new DataView(buffer), bytes = new Uint8Array(buffer)
const put = (offset, type, v) => {
if (type === 2) view.setUint8(offset, v)
else if (type === 3) view.setUint16(offset, v, true)
else if (type === 4) view.setUint32(offset, v, true)
else view.setFloat64(offset, v, true)
}
view.setUint16(0, 0x4949, true); view.setUint16(2, 42, true); view.setUint32(4, 8, true)
view.setUint16(8, entries.length, true)
entries.forEach(([tag, type, values], k) => {
const e = 10 + 12 * k
view.setUint16(e, tag, true); view.setUint16(e + 2, type, true); view.setUint32(e + 4, values.length, true)
const base = places[k] ?? e + 8
if (places[k] !== null) view.setUint32(e + 8, base, true)
values.forEach((v, i) => put(base + i * TIFF_BYTES[type], type, v))
})
bands.forEach((band, b) => bytes.set(new Uint8Array(band.buffer, band.byteOffset, plane), at + b * plane))
return new Blob([buffer], { type: 'image/tiff' })
}
/** The shown image (a PNG object URL), saved as drawn. */
export async function downloadImage(src, filename) {
save(await (await fetch(src)).blob(), filename)
}
/** int8 bands of the AlphaEarth window as a GeoTIFF. */
export function downloadAefTiff(window, filename) {
save(int8GeoTiff(window.bands, window.size, { epsg: window.cell.epsg, west: window.west, north: window.north, res: window.res }),
filename)
}
/** Python that reads the same AlphaEarth window: each tile's part (a windowed read; the files are stored south-up),
* joined, north up, on the 10 m grid the app uses. */
export function aefSnippet(cellName, sample, cell) {
const west = Math.round((cell.cx - 5340) / 10) * 10, north = Math.round((cell.cy - 5340) / 10) * 10 + 10680
return `import numpy as np
import rasterio
from rasterio.windows import Window
urls = ${JSON.stringify(cell.urls, null, 4).replace(/\n\]$/, ',\n]')}
west, north = ${west}, ${north} # ${cellName}, EPSG:${cell.epsg}: 1,068 x 1,068 pixels of 10 m
embedding = np.full((64, 1068, 1068), -128, np.int8) # AlphaEarth ${sample.year} (tge-labs/aef, CC-BY 4.0)
for url in urls: # every tile under the cell
with rasterio.open(url) as src:
t = src.transform # t.e > 0: stored south-up
edge = north - 10680 if t.e > 0 else north
win = Window(round((west - t.c) / t.a), round((edge - t.f) / t.e), 1068, 1068)
part = src.read(window=win, boundless=True, fill_value=-128)
if t.e > 0:
part = part[:, ::-1] # north up
embedding = np.where(embedding == -128, part, embedding)
`
}
|