mirror of
https://github.com/protomaps/PMTiles.git
synced 2026-02-04 10:51:07 +00:00
initial implementations of python v3 utility functions
This commit is contained in:
103
python/pmtiles/tile.py
Normal file
103
python/pmtiles/tile.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
Entry = namedtuple("Entry", ["tile_id", "offset", "length", "run_length"])
|
||||||
|
Header = namedtuple("Header", [])
|
||||||
|
|
||||||
|
|
||||||
|
def rotate(n, xy, rx, ry):
|
||||||
|
if ry == 0:
|
||||||
|
if rx == 1:
|
||||||
|
xy[0] = n - 1 - xy[0]
|
||||||
|
xy[1] = n - 1 - xy[1]
|
||||||
|
xy[0], xy[1] = xy[1], xy[0]
|
||||||
|
|
||||||
|
|
||||||
|
def t_on_level(z, pos):
|
||||||
|
n = 1 << z
|
||||||
|
rx, ry, t = pos, pos, pos
|
||||||
|
xy = [0, 0]
|
||||||
|
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 //= 4
|
||||||
|
s *= 2
|
||||||
|
return z, xy[0], xy[1]
|
||||||
|
|
||||||
|
|
||||||
|
def zxy_to_tileid(z, x, y):
|
||||||
|
acc = 0
|
||||||
|
tz = 0
|
||||||
|
while tz < z:
|
||||||
|
acc += (0x1 << tz) * (0x1 << tz)
|
||||||
|
tz += 1
|
||||||
|
n = 1 << z
|
||||||
|
rx = 0
|
||||||
|
ry = 0
|
||||||
|
d = 0
|
||||||
|
xy = [x, y]
|
||||||
|
s = n // 2
|
||||||
|
while s > 0:
|
||||||
|
if (xy[0] & s) > 0:
|
||||||
|
rx = 1
|
||||||
|
else:
|
||||||
|
rx = 0
|
||||||
|
if (xy[1] & s) > 0:
|
||||||
|
ry = 1
|
||||||
|
else:
|
||||||
|
ry = 0
|
||||||
|
d += s * s * ((3 * rx) ^ ry)
|
||||||
|
rotate(s, xy, rx, ry)
|
||||||
|
s //= 2
|
||||||
|
return acc + d
|
||||||
|
|
||||||
|
|
||||||
|
def tileid_to_zxy(tile_id):
|
||||||
|
num_tiles = 0
|
||||||
|
acc = 0
|
||||||
|
z = 0
|
||||||
|
while True:
|
||||||
|
num_tiles = (1 << z) * (1 << z)
|
||||||
|
if acc + num_tiles > tile_id:
|
||||||
|
return t_on_level(z, tile_id - acc)
|
||||||
|
acc += num_tiles
|
||||||
|
z += 1
|
||||||
|
|
||||||
|
|
||||||
|
def find_tile(entries, tile_id):
|
||||||
|
m = 0
|
||||||
|
n = len(entries) - 1
|
||||||
|
while m < n:
|
||||||
|
k = (n + m) >> 1
|
||||||
|
c = tile_id - entries[k].tile_id
|
||||||
|
if c > 0:
|
||||||
|
m = k + 1
|
||||||
|
elif c < 0:
|
||||||
|
n = k - 1
|
||||||
|
else:
|
||||||
|
return entries[k]
|
||||||
|
|
||||||
|
if n >= 0:
|
||||||
|
if entries[n].run_length == 0:
|
||||||
|
return entries[n]
|
||||||
|
if tile_id - entries[n].tile_id < entries[n].run_length:
|
||||||
|
return entries[n]
|
||||||
|
|
||||||
|
|
||||||
|
def deserialize_directory(bytes):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_directory(bytes):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def deserialize_header(bytes):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_header(bytes):
|
||||||
|
pass
|
||||||
60
python/test/test_tile.py
Normal file
60
python/test/test_tile.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import unittest
|
||||||
|
from pmtiles.tile import zxy_to_tileid, tileid_to_zxy, Entry, find_tile
|
||||||
|
|
||||||
|
|
||||||
|
class TestTileId(unittest.TestCase):
|
||||||
|
def test_zxy_to_tileid(self):
|
||||||
|
self.assertEqual(zxy_to_tileid(0, 0, 0), 0)
|
||||||
|
self.assertEqual(zxy_to_tileid(1, 0, 0), 1)
|
||||||
|
self.assertEqual(zxy_to_tileid(1, 0, 1), 2)
|
||||||
|
self.assertEqual(zxy_to_tileid(1, 1, 1), 3)
|
||||||
|
self.assertEqual(zxy_to_tileid(1, 1, 0), 4)
|
||||||
|
self.assertEqual(zxy_to_tileid(2, 0, 0), 5)
|
||||||
|
|
||||||
|
def test_tileid_to_zxy(self):
|
||||||
|
self.assertEqual(tileid_to_zxy(0), (0, 0, 0))
|
||||||
|
self.assertEqual(tileid_to_zxy(1), (1, 0, 0))
|
||||||
|
self.assertEqual(tileid_to_zxy(19078479), (12, 3423, 1763))
|
||||||
|
|
||||||
|
def test_many_tiles(self):
|
||||||
|
for z in range(0, 7):
|
||||||
|
for x in range(0, 1 << z):
|
||||||
|
for y in range(0, 1 << z):
|
||||||
|
i = zxy_to_tileid(z, x, y)
|
||||||
|
rz, rx, ry = tileid_to_zxy(i)
|
||||||
|
self.assertEqual(z, rz)
|
||||||
|
self.assertEqual(x, rx)
|
||||||
|
self.assertEqual(y, ry)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFindTile(unittest.TestCase):
|
||||||
|
def test_find_tile_missing(self):
|
||||||
|
entries = []
|
||||||
|
result = find_tile(entries, 0)
|
||||||
|
self.assertEqual(result, None)
|
||||||
|
|
||||||
|
def test_find_tile_first(self):
|
||||||
|
entries = [Entry(100, 1, 1, 1)]
|
||||||
|
result = find_tile(entries, 100)
|
||||||
|
self.assertEqual(result.offset, 1)
|
||||||
|
self.assertEqual(result.length, 1)
|
||||||
|
|
||||||
|
def test_find_tile_multiple(self):
|
||||||
|
entries = [Entry(100, 1, 1, 2)]
|
||||||
|
result = find_tile(entries, 101)
|
||||||
|
self.assertEqual(result.offset, 1)
|
||||||
|
self.assertEqual(result.length, 1)
|
||||||
|
entries = [Entry(100, 1, 1, 2), Entry(150, 2, 2, 2)]
|
||||||
|
result = find_tile(entries, 151)
|
||||||
|
self.assertEqual(result.offset, 2)
|
||||||
|
self.assertEqual(result.length, 2)
|
||||||
|
entries = [Entry(50, 1, 1, 2), Entry(100, 2, 2, 1), Entry(150, 3, 3, 1)]
|
||||||
|
result = find_tile(entries, 51)
|
||||||
|
self.assertEqual(result.offset, 1)
|
||||||
|
self.assertEqual(result.length, 1)
|
||||||
|
|
||||||
|
def test_find_tile_leaf(self):
|
||||||
|
entries = [Entry(100, 1, 1, 0)]
|
||||||
|
result = find_tile(entries, 150)
|
||||||
|
self.assertEqual(result.offset, 1)
|
||||||
|
self.assertEqual(result.length, 1)
|
||||||
Reference in New Issue
Block a user