Public
Edited
Sep 8, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
s = ({
evalscript : `
//VERSION=3
function setup() {
return ({
input: [{
bands:["B04", "B03", "B02"],
}],
output: [{
id: "default",
bands: 3,
sampleType: SampleType.${image_config.format.out_type}
}]
});
}

const typeFactor = ('${image_config.format.out_type}'=='UINT16') ? 65535 : 255;
function stretchType(arr, off, scale) {
if (!arr) { return arr;}
switch (arr.length) {
case 4: arr[3] = typeFactor * (off + arr[3]*scale);
case 3: arr[2] = typeFactor * (off + arr[2]*scale);
case 2: arr[1] = typeFactor * (off + arr[1]*scale);
default: arr[0] = typeFactor * (off + arr[0]*scale);
}
return arr;
}


//== PARAMETERS ===========================
const c0r = 0.031;
const tx = 0.1;
const ty0 = 0.1;
const ty1 = 1.0;
const max = 0.4;
const sat = 1.35;

const debug = false; // Set to 'true' to highlight out-of-range values

const atmRatios = [0.9, 1.6, 2.7]; // Rayleigh-derived consts for automated atmosphere offsets

//== FUNCTIONS ============================
const sRGBenc = C => C < 0.0031308 ? (12.92 * C) : (1.055 * Math.pow(C, 0.41666) - 0.055);

// atmospheric adjustment
const atm2p = (a, c0, c1) => (a - c0) / c1;

const atm1p = (a, c0) => atm2p(a, c0, (1 - c0)**2);

const atm = (a, ii) => (typeof cManual !== 'undefined')
? (cManual[ii] instanceof Array)
? atm2p(a, cManual[ii][0], cManual[ii][1])
: atm1p(a, cManual[ii])
: atm1p(a, c0r * atmRatios[ii]);

//contrast enhancement
const adjFun = (a, cx, cy, cmax) => {
const ar = a / cmax;
const txr = cx / cmax;
const bot = (2 * txr - 1) * ar - txr;
return ar * (1 + (txr - cy) * (1 - ar) / bot);
};

const adj2 = (a, cx, cy0, cy1, max) => {
const blend = adjFun(a, 0.5 * max, (max - cx)/max, max);
return (1 - blend) * adjFun(a, cx, cy0, max) + blend * adjFun(a, cx, cy1, max);
}

const adj = a => adj2(a, tx, ty0, ty1, max);

const satEnh = rgbArr => {
const avg = (rgbArr[0]+rgbArr[1]+rgbArr[2])/3.0;
return [
avg * (1 - sat) + rgbArr[0] * sat,
avg * (1 - sat) + rgbArr[1] * sat,
avg * (1 - sat) + rgbArr[2] * sat
]
};

const checkDebug = arr => {
if (!debug) {
return arr;
}
const minC = Math.min(arr[0], arr[1], arr[2]);
if (minC < 0) {
return [
arr[0] < 0 ? 1 : 0,
arr[1] < 0 ? 1 : 0,
arr[2] < 0 ? 1 : 0
];
}
const maxC = Math.max(arr[0], arr[1], arr[2]);
if (maxC > 1) {
return [
arr[0] > 1 ? 1 : 0,
arr[1] > 1 ? 1 : 0,
arr[2] > 1 ? 1 : 0
];
}
return arr;
};

//== SCRIPT ============================
function evaluatePixel(sample) {
const rgb = satEnh([
adj(atm(sample.B04, 0)),
adj(atm(sample.B03, 1)),
adj(atm(sample.B02, 2))
]);

const retLin = checkDebug(rgb);
return stretchType([
sRGBenc(retLin[0]),
sRGBenc(retLin[1]),
sRGBenc(retLin[2])
], 0, 1);
}
`
})
Insert cell
image_config = ({
format: PNG, // PNG (8-bit) or TIF (16-bit)
date: '2019-11-16',
// center
lat: 38.66,
lon: -9.26,
// real-world pixel size for the final image
resolution: 20,
// size of the final image in pixels
img_w: 3400,
img_h: Math.round(3400*594/420),
img_split: 2500, //max size of the tile
preview_scale: 8 //e.g. 8 for thumbnail preview. Use 1 for the final download
});
Insert cell
Insert cell
Insert cell
Insert cell
tiles_arr = {
const res = image_params.resolution;
const tilesX = Math.ceil(image_params.img_w / image_params.img_split);
const tilesY = Math.ceil(image_params.img_h / image_params.img_split);
const rows = [];
let topYpx=0;
for (let y = 0; y < tilesY; y++) {
const cells = [];
const botYpx = Math.round((y+1)/tilesY * image_params.img_h);
let leftXpx = 0;
for (let x = 0; x < tilesX; x++) {
const rightXpx = Math.round((x+1)/tilesX * image_params.img_w);
const min_e = image_params.bbox[0] + res * leftXpx;
const max_n = image_params.bbox[3] - res * topYpx;
const tile_h = botYpx - topYpx;
const tileURL = await fetch_tile(min_e, max_n, res, rightXpx - leftXpx, tile_h);
cells.push(tileURL);
leftXpx = rightXpx;
}
topYpx = botYpx;
rows.push(cells);
}
return rows;
}
Insert cell
image_params = {
const utm_zone = Math.ceil(image_config.lon/6) + 30;
const utm = proj4(`+proj=utm +zone=${utm_zone} +datum=WGS84 +units=m +no_defs`);
const preview = image_config.preview_scale;
const en = utm.forward([image_config.lon, image_config.lat]);
const res = image_config.resolution * preview;
const half_w = Math.round(image_config.img_w / 2 / preview);
const half_h = Math.round(image_config.img_h / 2 / preview);
const center_e = Math.round(en[0]/res) * res;
const center_n = Math.round(en[1]/res) * res;
return ({
...image_config.format,
...image_config,
resolution: res,
utm_zone: utm_zone,
img_w: 2 * half_w,
img_h: 2 * half_h,
img_split: image_config.img_split / preview,
center_e: center_e,
center_n: center_n,
bbox: [center_e - half_w * res, center_n - half_h * res, center_e + half_w * res, center_n + half_h * res],
evalscript: s.evalscript
})
}
Insert cell
function fetch_tile(min_e, max_n, res, w, h) {
return instance.post('https://services.sentinel-hub.com/api/v1/process',
{
"input": {
"bounds": {
"bbox": [min_e, max_n - res * h, min_e + res * w, max_n],
"properties": {
"crs": "http://www.opengis.net/def/crs/EPSG/0/326"+image_params.utm_zone
}
},
"data": [{
"type": "S2L1C",
"dataFilter": {
"timeRange": {
"from": image_params.date + "T00:00:00Z",
"to": image_params.date + "T23:59:59Z"
}
}
}]
},
"output": {
"width": w,
"height": h,
"responses": [
{
"identifier": "default",
"format": {
"type": image_params.mime_type
}
}
]
},
"evalscript": image_params.evalscript
},
{
responseType: 'blob'
}
).then(resp => URL.createObjectURL(resp.data))
}
Insert cell
/*instance.post('https://cors-anywhere.herokuapp.com/services.sentinel-hub.com/api/v1/catalog/search',
{
"bbox": [13,45,14,46],
"datetime": "2019-12-10T00:00:00Z/2019-12-10T23:59:59Z",
"collections": ["sentinel-1-grd"],
"limit": 5
})*/
Insert cell
TIF = ({
out_type: 'UINT16',
mime_type: 'image/tiff',
image_suffix: 'tif'
})
Insert cell
PNG = ({
out_type: 'UINT8',
mime_type: 'image/png',
image_suffix: 'png'
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more