mirror of
https://github.com/protomaps/PMTiles.git
synced 2026-02-04 10:51:07 +00:00
base v3 tileID and lookup functions [#41]
This commit is contained in:
@@ -11,6 +11,8 @@ import {
|
|||||||
createDirectory,
|
createDirectory,
|
||||||
} from "./index";
|
} from "./index";
|
||||||
|
|
||||||
|
import { Entry as EntryV3, zxyToTileId, tileIdToZxy, findTile } from "./v3";
|
||||||
|
|
||||||
test("stub data", (assertion) => {
|
test("stub data", (assertion) => {
|
||||||
let dataview = createDirectory([
|
let dataview = createDirectory([
|
||||||
{ z: 5, x: 1000, y: 2000, offset: 1000, length: 2000, is_dir: false },
|
{ z: 5, x: 1000, y: 2000, offset: 1000, length: 2000, is_dir: false },
|
||||||
@@ -145,3 +147,78 @@ test("convert spec v1 directory to spec v2 directory", (assertion) => {
|
|||||||
entry = parseEntry(view, 2);
|
entry = parseEntry(view, 2);
|
||||||
assertion.ok(entry!.offset === 3);
|
assertion.ok(entry!.offset === 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("zxy to tile id", (assertion) => {
|
||||||
|
assertion.eq(zxyToTileId(0, 0, 0), 0);
|
||||||
|
assertion.eq(zxyToTileId(1, 0, 0), 1);
|
||||||
|
assertion.eq(zxyToTileId(1, 0, 1), 2);
|
||||||
|
assertion.eq(zxyToTileId(1, 1, 1), 3);
|
||||||
|
assertion.eq(zxyToTileId(1, 1, 0), 4);
|
||||||
|
assertion.eq(zxyToTileId(2, 0, 0), 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("tile id to zxy", (assertion) => {
|
||||||
|
assertion.eq(tileIdToZxy(0), [0, 0, 0]);
|
||||||
|
assertion.eq(tileIdToZxy(1), [1, 0, 0]);
|
||||||
|
assertion.eq(tileIdToZxy(2), [1, 0, 1]);
|
||||||
|
assertion.eq(tileIdToZxy(3), [1, 1, 1]);
|
||||||
|
assertion.eq(tileIdToZxy(4), [1, 1, 0]);
|
||||||
|
assertion.eq(tileIdToZxy(5), [2, 0, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a lot of tiles", (assertion) => {
|
||||||
|
for (var z = 0; z < 9; z++) {
|
||||||
|
for (var x = 0; x < 1 << z; x++) {
|
||||||
|
for (var y = 0; y < 1 << z; y++) {
|
||||||
|
let result = tileIdToZxy(zxyToTileId(z, x, y));
|
||||||
|
if (result[0] !== z || result[1] !== x || result[2] !== y) {
|
||||||
|
assertion.fail("roundtrip failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("tile search for first entry == id", (assertion) => {
|
||||||
|
let entries: EntryV3[] = [];
|
||||||
|
assertion.eq(findTile(entries, 101), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("tile search for first entry == id", (assertion) => {
|
||||||
|
let entries: EntryV3[] = [{ tileId: 100, offset: 1, length: 1, runLength: 1 }];
|
||||||
|
let entry = findTile(entries, 100)!;
|
||||||
|
assertion.eq(entry.offset, 1);
|
||||||
|
assertion.eq(entry.length, 1);
|
||||||
|
assertion.eq(findTile(entries, 101), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("tile search for first entry == id", (assertion) => {
|
||||||
|
let entries: EntryV3[] = [{ tileId: 100, offset: 1, length: 1, runLength: 2 }];
|
||||||
|
let entry = findTile(entries, 101)!;
|
||||||
|
assertion.eq(entry.offset, 1);
|
||||||
|
assertion.eq(entry.length, 1);
|
||||||
|
|
||||||
|
entries = [
|
||||||
|
{ tileId: 100, offset: 1, length: 1, runLength: 1 },
|
||||||
|
{ tileId: 150, offset: 2, length: 2, runLength: 2 },
|
||||||
|
];
|
||||||
|
entry = findTile(entries, 151)!;
|
||||||
|
assertion.eq(entry.offset, 2);
|
||||||
|
assertion.eq(entry.length, 2);
|
||||||
|
|
||||||
|
entries = [
|
||||||
|
{ tileId: 50, offset: 1, length: 1, runLength: 2 },
|
||||||
|
{ tileId: 100, offset: 2, length: 2, runLength: 1 },
|
||||||
|
{ tileId: 150, offset: 3, length: 3, runLength: 1 },
|
||||||
|
];
|
||||||
|
entry = findTile(entries, 51)!;
|
||||||
|
assertion.eq(entry.offset, 1);
|
||||||
|
assertion.eq(entry.length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("leaf search", (assertion) => {
|
||||||
|
let entries: EntryV3[] = [{ tileId: 100, offset: 1, length: 1, runLength: 0 }];
|
||||||
|
let entry = findTile(entries, 150);
|
||||||
|
assertion.eq(entry!.offset, 1);
|
||||||
|
assertion.eq(entry!.length, 1);
|
||||||
|
});
|
||||||
|
|||||||
101
js/v3.ts
Normal file
101
js/v3.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
function rotate(n: number, xy: number[], rx: number, ry: number): void {
|
||||||
|
if (ry == 0) {
|
||||||
|
if (rx == 1) {
|
||||||
|
xy[0] = n - 1 - xy[0];
|
||||||
|
xy[1] = n - 1 - xy[1];
|
||||||
|
}
|
||||||
|
let t = xy[0];
|
||||||
|
xy[0] = xy[1];
|
||||||
|
xy[1] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function idOnLevel(z: number, pos: number): [number, number, number] {
|
||||||
|
let n = 1 << z;
|
||||||
|
let rx = pos;
|
||||||
|
let ry = pos;
|
||||||
|
let t = pos;
|
||||||
|
let xy = [0, 0];
|
||||||
|
let s = 1;
|
||||||
|
while (s < n) {
|
||||||
|
rx = 1 & ((t / 2) >> 0);
|
||||||
|
ry = 1 & (t ^ rx);
|
||||||
|
rotate(s, xy, rx, ry);
|
||||||
|
xy[0] += s * rx;
|
||||||
|
xy[1] += s * ry;
|
||||||
|
t = (t / 4) >> 0;
|
||||||
|
s *= 2;
|
||||||
|
}
|
||||||
|
return [z, xy[0], xy[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function zxyToTileId(z: number, x: number, y: number): number {
|
||||||
|
let acc = 0;
|
||||||
|
let tz = 0;
|
||||||
|
while (tz < z) {
|
||||||
|
acc += (0x1 << tz) * (0x1 << tz);
|
||||||
|
tz++;
|
||||||
|
}
|
||||||
|
let n = 1 << z;
|
||||||
|
let rx = 0;
|
||||||
|
let ry = 0;
|
||||||
|
let d = 0;
|
||||||
|
let xy = [x, y];
|
||||||
|
let s = (n / 2) >> 0;
|
||||||
|
while (s > 0) {
|
||||||
|
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);
|
||||||
|
s = (s / 2) >> 0;
|
||||||
|
}
|
||||||
|
return acc + d;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tileIdToZxy(i: number): [number, number, number] {
|
||||||
|
let acc = 0;
|
||||||
|
let z = 0;
|
||||||
|
while (true) {
|
||||||
|
let num_tiles = (0x1 << z) * (0x1 << z);
|
||||||
|
if (acc + num_tiles > i) {
|
||||||
|
return idOnLevel(z, i - acc);
|
||||||
|
}
|
||||||
|
acc += num_tiles;
|
||||||
|
z++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Entry {
|
||||||
|
tileId: number;
|
||||||
|
offset: number;
|
||||||
|
length: number;
|
||||||
|
runLength: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findTile(entries: Entry[], tileId: number): Entry | null {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user