s2-tools
Version:
A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.
243 lines • 7.29 kB
JavaScript
import { readVarint } from './varint';
/**
* Enum representing a compression algorithm used.
* 0 = unknown compression, for if you must use a different or unspecified algorithm.
* 1 = no compression.
* 2 = gzip
* 3 = brotli
* 4 = zstd
*/
export var Compression;
(function (Compression) {
/** unknown compression, for if you must use a different or unspecified algorithm. */
Compression[Compression["Unknown"] = 0] = "Unknown";
/** no compression. */
Compression[Compression["None"] = 1] = "None";
/** gzip. */
Compression[Compression["Gzip"] = 2] = "Gzip";
/** brotli. */
Compression[Compression["Brotli"] = 3] = "Brotli";
/** zstd. */
Compression[Compression["Zstd"] = 4] = "Zstd";
})(Compression || (Compression = {}));
/**
* Describe the type of tiles stored in the archive.
* 0 is unknown/other, 1 is a vector tile spec.
*/
export var TileType;
(function (TileType) {
/** unknown/other. */
TileType[TileType["Unknown"] = 0] = "Unknown";
/** Vector tiles. */
TileType[TileType["Pbf"] = 1] = "Pbf";
/** Image tiles. */
TileType[TileType["Png"] = 2] = "Png";
/** Image tiles. */
TileType[TileType["Jpeg"] = 3] = "Jpeg";
/** Image tiles. */
TileType[TileType["Webp"] = 4] = "Webp";
/** Image tiles. */
TileType[TileType["Avif"] = 5] = "Avif";
})(TileType || (TileType = {}));
export const HEADER_SIZE_BYTES = 127;
export const ROOT_SIZE = 16_384;
/**
* Rotate a point by n degrees.
* @param n - the rotation size
* @param xy - the point
* @param rx - the x rotation
* @param ry - the y rotation
*/
function rotate(n, xy, rx, ry) {
if (ry === 0) {
if (rx === 1) {
xy[0] = n - 1 - xy[0];
xy[1] = n - 1 - xy[1];
}
const t = xy[0];
xy[0] = xy[1];
xy[1] = t;
}
}
/**
* Get the tile ID on the given level.
* @param zoom - the zoom level
* @param pos - the tile position
* @returns - the tile
*/
function idOnLevel(zoom, pos) {
const n = 2 ** zoom;
let rx = pos;
let ry = pos;
let t = pos;
const xy = [0, 0];
let s = 1;
while (s < n) {
rx = 1 & (t / 2);
ry = 1 & (t ^ rx);
rotate(s, xy, rx, ry);
xy[0] += s * rx;
xy[1] += s * ry;
t = t / 4;
s *= 2;
}
return [zoom, xy[0], xy[1]];
}
const tzValues = [
0, 1, 5, 21, 85, 341, 1365, 5461, 21845, 87381, 349525, 1398101, 5592405, 22369621, 89478485,
357913941, 1431655765, 5726623061, 22906492245, 91625968981, 366503875925, 1466015503701,
5864062014805, 23456248059221, 93824992236885, 375299968947541, 1501199875790165,
];
/**
* Convert Z,X,Y to a Hilbert TileID.
* @param zoom - the zoom level
* @param x - the x coordinate
* @param y - the y coordinate
* @returns - the Hilbert encoded TileID
*/
export function zxyToTileID(zoom, x, y) {
if (zoom > 26) {
throw Error('Tile zoom level exceeds max safe number limit (26)');
}
if (x > 2 ** zoom - 1 || y > 2 ** zoom - 1) {
throw Error('tile x/y outside zoom level bounds');
}
const acc = tzValues[zoom];
const n = 2 ** zoom;
let rx = 0;
let ry = 0;
let d = 0;
const xy = [x, y];
let s = n / 2;
while (true) {
rx = (xy[0] & s) > 0 ? 1 : 0;
ry = (xy[1] & s) > 0 ? 1 : 0;
d += s * s * ((3 * rx) ^ ry);
rotate(s, xy, rx, ry);
if (s <= 1)
break;
s = s / 2;
}
return acc + d;
}
/**
* Convert a Hilbert TileID to Z,X,Y.
* @param i - the encoded tile ID
* @returns - the decoded Z,X,Y
*/
export function tileIDToZxy(i) {
let acc = 0;
for (let z = 0; z < 27; z++) {
const numTiles = (0x1 << z) * (0x1 << z);
if (acc + numTiles > i) {
return idOnLevel(z, i - acc);
}
acc += numTiles;
}
throw Error('Tile zoom level exceeds max safe number limit (26)');
}
/**
* Low-level function for looking up a TileID or leaf directory inside a directory.
* @param entries - the directory entries
* @param tileID - the tile ID
* @returns the entry associated with the tile, or null if not found
*/
export function findTile(entries, tileID) {
let m = 0;
let n = entries.length - 1;
while (m <= n) {
const k = (n + m) >> 1;
const cmp = tileID - entries[k].tileID;
if (cmp > 0) {
m = k + 1;
}
else if (cmp < 0) {
n = k - 1;
}
else {
return entries[k];
}
}
// at this point, m > n
if (n >= 0) {
if (entries[n].runLength === 0)
return entries[n];
if (tileID - entries[n].tileID < entries[n].runLength)
return entries[n];
}
return null;
}
/**
* Parse raw header bytes into a Header object.
* @param bytes - the raw header bytes
* @returns the parsed header
*/
export function bytesToHeader(bytes) {
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
// if (dv.getUint16(0, true) !== 0x4d50) {
// throw new Error('Wrong magic number for PMTiles archive');
// }
return {
specVersion: dv.getUint8(7),
rootDirectoryOffset: getUint64(dv, 8),
rootDirectoryLength: getUint64(dv, 16),
jsonMetadataOffset: getUint64(dv, 24),
jsonMetadataLength: getUint64(dv, 32),
leafDirectoryOffset: getUint64(dv, 40),
leafDirectoryLength: getUint64(dv, 48),
tileDataOffset: getUint64(dv, 56),
tileDataLength: getUint64(dv, 64),
numAddressedTiles: getUint64(dv, 72),
numTileEntries: getUint64(dv, 80),
numTileContents: getUint64(dv, 88),
clustered: dv.getUint8(96) === 1,
internalCompression: dv.getUint8(97),
tileCompression: dv.getUint8(98),
tileType: dv.getUint8(99),
minZoom: dv.getUint8(100),
maxZoom: dv.getUint8(101),
};
}
/**
* Deserialize a directory from a Uint8Array.
* @param buffer - the buffer to deserialize
* @returns - the deserialized entries
*/
export function deserializeDir(buffer) {
const p = { buf: new Uint8Array(buffer), pos: 0 };
const numEntries = readVarint(p);
const entries = [];
let lastID = 0;
for (let i = 0; i < numEntries; i++) {
const v = readVarint(p);
entries.push({ tileID: lastID + v, offset: 0, length: 0, runLength: 1 });
lastID += v;
}
// run lengths, lengths, and offsets
for (let i = 0; i < numEntries; i++)
entries[i].runLength = readVarint(p);
for (let i = 0; i < numEntries; i++)
entries[i].length = readVarint(p);
for (let i = 0; i < numEntries; i++) {
const v = readVarint(p);
if (v === 0 && i > 0) {
entries[i].offset = entries[i - 1].offset + entries[i - 1].length;
}
else {
entries[i].offset = v - 1;
}
}
return entries;
}
/**
* Get a 64-bit number from a DataView
* @param dv - a DataView
* @param offset - the offset in the DataView
* @returns - the decoded 64-bit number
*/
export function getUint64(dv, offset) {
const wh = dv.getUint32(offset + 4, true);
const wl = dv.getUint32(offset, true);
return wh * 2 ** 32 + wl;
}
//# sourceMappingURL=pmtiles.js.map