SOURCE / PINNED RELEASE
Made of little things.
Atlas map viewer
- Release
- 1d2807e80b44…
- Author-recorded commit
- 43810ed2b368…
- License
- LICENSE
- Author’s source reference
- nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-4757dbabc86
Archive hash verified: 8db6876d96c460ac…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import difference from "@turf/difference";
import intersects from "@turf/boolean-intersects";
import type { Feature, Polygon, MultiPolygon } from "geojson";
import {
MAP_LAYERS,
project,
tileLayer,
type Block,
type Manifest,
type MapLayer,
} from "../../format/src/index";
import type { AtlasReader } from "../../format/src/reader";
import { inside } from "../../offline/src/geometry";
export type Coverage = Feature<Polygon | MultiPolygon>;
export type Channel = MapLayer | "labels";
export interface CompositionSource {
id: string;
manifest: Manifest;
enabled: boolean;
layers: MapLayer[];
labels: boolean;
}
export interface CoveragePart {
source: string;
channel: Channel;
coverage: Coverage;
}
export function rectangle(
w: number,
n: number,
e: number,
s: number,
): Coverage {
return {
type: "Feature",
properties: {},
geometry: {
type: "Polygon",
coordinates: [
[
[w, n],
[e, n],
[e, s],
[w, s],
[w, n],
],
],
},
};
}
export function coverageOf(manifest: Manifest): Coverage {
const [w, s, e, n] = manifest.bounds;
const ring = (
manifest.selection?.polygon ?? [
[w, s],
[e, s],
[e, n],
[w, n],
]
).map((p) => project(p[0]!, p[1]!));
if (ring[0]![0] !== ring.at(-1)![0] || ring[0]![1] !== ring.at(-1)![1])
ring.push(ring[0]!);
return {
type: "Feature",
properties: {},
geometry: { type: "Polygon", coordinates: [ring] },
};
}
export function polygonsOf(coverage: Coverage) {
return coverage.geometry.type === "Polygon"
? [coverage.geometry.coordinates]
: coverage.geometry.coordinates;
}
export function coverageBounds(
coverage: Coverage,
): [number, number, number, number] {
const points = polygonsOf(coverage).flat(2);
return [
Math.min(...points.map((p) => p[0]!)),
Math.min(...points.map((p) => p[1]!)),
Math.max(...points.map((p) => p[0]!)),
Math.max(...points.map((p) => p[1]!)),
];
}
export function contains(coverage: Coverage, point: [number, number]) {
return polygonsOf(coverage).some(
(rings) =>
inside(point, rings[0]! as [number, number][]) &&
!rings.slice(1).some((r) => inside(point, r as [number, number][])),
);
}
/** First source owns each selected channel inside its advertised polygon. Other
* channels pass through. Empty water is authoritative; it is not missing data. */
export function planComposition(
sources: readonly CompositionSource[],
zoom: number,
): CoveragePart[] {
const result: CoveragePart[] = [];
for (const channel of [...MAP_LAYERS, "labels"] as Channel[]) {
const claimed: Coverage[] = [];
for (const source of sources) {
if (!source.enabled || zoom < source.manifest.minZoom) continue;
const has =
channel === "labels"
? source.labels && source.manifest.capabilities.includes("map-labels")
: source.layers.includes(channel) &&
(source.manifest.tileLayers ?? MAP_LAYERS).includes(channel);
if (!has) continue;
const whole = coverageOf(source.manifest);
const coverage = claimed.length
? difference({
type: "FeatureCollection",
features: [whole, ...claimed],
})
: whole;
if (coverage) result.push({ source: source.id, channel, coverage });
claimed.push(whole);
}
}
return result;
}
export function clipArrays(coverage: Coverage) {
const polygons = polygonsOf(coverage);
return {
coordinates: new Float64Array(polygons.flat(3)),
rings: new Uint32Array(polygons.flat().map((r) => r.length)),
polygons: new Uint32Array(polygons.map((p) => p.length)),
};
}
/** Keeps the original reader's snapshot/cache identity and skips blocks whose
* entire cell is hidden by higher-priority coverage. Payloads stay immutable. */
export class CoveredTiles {
parts = new Map<Channel, Coverage>();
constructor(readonly reader: AtlasReader) {}
private filter(blocks: Block[]) {
return blocks.filter((b) => {
const layer = tileLayer(b.key);
const n = 2 ** b.z!;
const cell = rectangle(
b.x! / n,
b.y! / n,
(b.x! + 1) / n,
(b.y! + 1) / n,
);
return (layer ? [this.parts.get(layer)] : [...this.parts.values()]).some(
(p) => p && intersects(p, cell),
);
});
}
tilesCached(...args: Parameters<AtlasReader["tilesCached"]>) {
const r = this.reader.tilesCached(...args);
return { complete: r.complete, blocks: this.filter(r.blocks) };
}
async tiles(...args: Parameters<AtlasReader["tiles"]>) {
return this.filter(await this.reader.tiles(...args));
}
block(key: string) {
return this.reader.block(key);
}
}
