feat(layers): Recent Imagery — open satellite imagery for a selected box (#716)

Select a box on the world and browse the last 30 days of open satellite imagery over it, keyless: NASA GIBS Harmonized Landsat Sentinel-2 (30 m) and the VIIRS daily overview (250 m), with a day strip, IMAGE / VS BASEMAP / A / B modes with a swipe divider, PNG export and share links. The before/after swipe from the Nepal scene moves into shared modules so both use it.

Co-authored-by: manjunath22466 <[email protected]>
This commit is contained in:
Bilawal Sidhuandmanjunath22466 authored and GitHub committed 2026-09-22 20:34:11 -05:00
1 parent 25491f7e33
commit 082074a006
50 files changed
+13169 -440

No files matched your search

+124
View File
@@ -0,0 +1,124 @@
/** Basemap that before/after imagery comparisons are read against. */
export const IMAGERY_COMPARISON_STACK_ID = 'esri-imagery';
const SWITCH_POLICIES = new Set(['preserve', 'esri', 'esri-if-photoreal']);
const leases = new WeakMap();
function comparisonTargetId(activeId, switchPolicy) {
if (!SWITCH_POLICIES.has(switchPolicy))
throw new Error(
`Unknown imagery comparison switch policy: ${switchPolicy}`,
);
if (switchPolicy === 'esri' && activeId !== IMAGERY_COMPARISON_STACK_ID)
return IMAGERY_COMPARISON_STACK_ID;
if (switchPolicy === 'esri-if-photoreal' && activeId === 'photoreal')
return IMAGERY_COMPARISON_STACK_ID;
return null;
}
/**
* Lease the map for an imagery comparison.
*
* Works on any controller exposing getActiveId(), getSwitchGeneration() and
* setStack(id); MapSourceController#acquireImageryComparison delegates here.
* One lease per controller: acquiring while another owner holds it throws
* synchronously. The previous stack id and the switch generation are recorded
* the moment the switch is issued (not after awaiting it), so release() can
* tell its own switch from a later operator choice. `ready` resolves to
* `{ status: 'ready' | 'superseded' | 'failed', activeId }`; a resolved
* setStack() does not mean success because the controller falls back
* internally. release() is idempotent and restores the previous stack only
* while the lease's own generation still owns the active stack; releasing
* during acquisition re-issues the previous stack so the late activation
* cannot survive. The lease stays owned until that restoration settles (a
* rival acquiring meanwhile still throws), so nobody can read the comparison
* stack as their own while it is on its way out; a release with nothing to
* restore frees the lease synchronously.
*/
export function acquireImageryComparison(
controller,
{ owner = 'imagery-comparison', switchPolicy = 'preserve' } = {},
) {
if (!controller)
throw new Error('Imagery comparison needs a map stack controller');
const held = leases.get(controller);
if (held)
throw new Error(
held.releasing
? `Imagery comparison is still held by ${held.owner} until its release settles`
: `Imagery comparison is already held by ${held.owner}`,
);
const previousId = controller.getActiveId?.() ?? null;
const targetId = comparisonTargetId(previousId, switchPolicy);
const lease = {
owner,
generation: null,
acquiring: false,
releasing: null,
};
leases.set(controller, lease);
const activeId = () => controller.getActiveId?.() ?? null;
const generation = () => controller.getSwitchGeneration?.() ?? null;
let ready;
if (targetId) {
lease.acquiring = true;
let switching;
try {
switching = Promise.resolve(controller.setStack(targetId));
} catch (error) {
switching = Promise.reject(error);
}
lease.generation = generation();
const settle = () => {
lease.acquiring = false;
};
ready = switching.then(
() => {
settle();
const active = activeId();
const status =
generation() !== lease.generation
? 'superseded'
: active === targetId
? 'ready'
: 'failed';
return { status, activeId: active };
},
(error) => {
settle();
throw error;
},
);
} else {
ready = Promise.resolve({ status: 'ready', activeId: previousId });
}
const disown = () => {
if (leases.get(controller) === lease) leases.delete(controller);
};
const release = () => {
if (lease.releasing) return lease.releasing;
const restore =
Boolean(targetId) &&
previousId != null &&
previousId !== targetId &&
generation() === lease.generation &&
(lease.acquiring || activeId() === targetId);
if (!restore) {
disown();
lease.releasing = Promise.resolve();
return lease.releasing;
}
lease.releasing = (async () => {
try {
await controller.setStack(previousId);
} finally {
disown();
}
})();
return lease.releasing;
};
return { ready, release };
}