Skip to content

Commit 8ea4ca3

Browse files
authored
Optimize Hilbert tile ID <-> XYZ conversion (#208)
1 parent 548bc7e commit 8ea4ca3

File tree

2 files changed

+37
-68
lines changed

2 files changed

+37
-68
lines changed

pmtiles/tile_id.go

+29-65
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package pmtiles
22

3+
import (
4+
"math/bits"
5+
)
6+
37
func rotate(n uint64, x *uint64, y *uint64, rx uint64, ry uint64) {
48
if ry == 0 {
59
if rx == 1 {
@@ -10,82 +14,42 @@ func rotate(n uint64, x *uint64, y *uint64, rx uint64, ry uint64) {
1014
}
1115
}
1216

13-
func tOnLevel(z uint8, pos uint64) (uint8, uint32, uint32) {
14-
var n uint64 = 1 << z
15-
rx, ry, t := pos, pos, pos
16-
var tx uint64
17-
var ty uint64
18-
var s uint64
19-
for s = 1; s < n; s *= 2 {
20-
rx = 1 & (t / 2)
21-
ry = 1 & (t ^ rx)
22-
rotate(s, &tx, &ty, rx, ry)
23-
tx += s * rx
24-
ty += s * ry
25-
t /= 4
26-
}
27-
return uint8(z), uint32(tx), uint32(ty)
28-
}
29-
3017
// ZxyToID converts (Z,X,Y) tile coordinates to a Hilbert TileID.
3118
func ZxyToID(z uint8, x uint32, y uint32) uint64 {
32-
var acc uint64
33-
var tz uint8
34-
for ; tz < z; tz++ {
35-
acc += (0x1 << tz) * (0x1 << tz)
36-
}
37-
var n uint64 = 1 << z
38-
var rx uint64
39-
var ry uint64
40-
var d uint64
41-
tx := uint64(x)
42-
ty := uint64(y)
43-
for s := n / 2; s > 0; s /= 2 {
44-
if tx&s > 0 {
45-
rx = 1
46-
} else {
47-
rx = 0
48-
}
49-
if ty&s > 0 {
50-
ry = 1
51-
} else {
52-
ry = 0
53-
}
54-
d += s * s * ((3 * rx) ^ ry)
19+
var acc uint64 = ((1 << (z * 2)) - 1) / 3
20+
var tx, ty uint64 = uint64(x), uint64(y)
21+
for a := int32(z - 1); a >= 0; a-- {
22+
var rx uint64 = (tx >> a) & 1
23+
var ry uint64 = (ty >> a) & 1
24+
var s uint64 = (1 << a)
5525
rotate(s, &tx, &ty, rx, ry)
26+
acc += s * s * ((3 * rx) ^ ry)
5627
}
57-
return acc + d
28+
return acc
5829
}
5930

6031
// IDToZxy converts a Hilbert TileID to (Z,X,Y) tile coordinates.
6132
func IDToZxy(i uint64) (uint8, uint32, uint32) {
62-
var acc uint64
63-
var z uint8
64-
for {
65-
var numTiles uint64
66-
numTiles = (1 << z) * (1 << z)
67-
if acc+numTiles > i {
68-
return tOnLevel(z, i-acc)
69-
}
70-
acc += numTiles
71-
z++
33+
var z uint8 = uint8((64 - bits.LeadingZeros64(3*i+1) - 1) / 2)
34+
var acc uint64 = (1<<(z*2) - 1) / 3
35+
var pos uint64 = i - acc
36+
var tx, ty uint64 = 0, 0
37+
for a := uint8(0); a < z; a++ {
38+
var rx uint64 = (pos / 2) & 1
39+
var ry uint64 = (pos ^ rx) & 1
40+
var s uint64 = 1 << a
41+
rotate(s, &tx, &ty, rx, ry)
42+
tx += s * rx
43+
ty += s * ry
44+
pos /= 4
7245
}
46+
return z, uint32(tx), uint32(ty)
7347
}
7448

7549
// ParentID efficiently finds a parent Hilbert TileID without converting to (Z,X,Y).
7650
func ParentID(i uint64) uint64 {
77-
var acc uint64
78-
var lastAcc uint64
79-
var z uint8
80-
for {
81-
var numTiles uint64
82-
numTiles = (1 << z) * (1 << z)
83-
if acc+numTiles > i {
84-
return lastAcc + (i-acc)/4
85-
}
86-
lastAcc = acc
87-
acc += numTiles
88-
z++
89-
}
90-
51+
var z uint8 = uint8((64 - bits.LeadingZeros64(3*i+1) - 1) / 2)
52+
var acc uint64 = (1<<(z*2) - 1) / 3
53+
var parentAcc uint64 = (1<<((z-1)*2) - 1) / 3
54+
return parentAcc + (i-acc)/4
9155
}

pmtiles/tile_id_test.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package pmtiles
22

33
import (
4-
"github.com/stretchr/testify/assert"
54
"testing"
5+
6+
"github.com/stretchr/testify/assert"
67
)
78

89
func TestZxyToId(t *testing.T) {
@@ -49,8 +50,7 @@ func TestManyTileIds(t *testing.T) {
4950
func TestExtremes(t *testing.T) {
5051
var tz uint8
5152
for tz = 0; tz < 32; tz++ {
52-
var dim uint32
53-
dim = (1 << tz) - 1
53+
var dim uint32 = (1 << tz) - 1
5454
z, x, y := IDToZxy(ZxyToID(tz, 0, 0))
5555
assert.Equal(t, tz, z)
5656
assert.Equal(t, uint32(0), x)
@@ -92,4 +92,9 @@ func TestParent(t *testing.T) {
9292
assert.Equal(t, ZxyToID(1, 1, 1), ParentID(ZxyToID(2, 2, 3)))
9393
assert.Equal(t, ZxyToID(1, 1, 1), ParentID(ZxyToID(2, 3, 2)))
9494
assert.Equal(t, ZxyToID(1, 1, 1), ParentID(ZxyToID(2, 3, 3)))
95+
96+
assert.Equal(t, ZxyToID(18, 500, 1), ParentID(ZxyToID(19, 1000, 3)))
97+
assert.Equal(t, ZxyToID(18, 500, 2), ParentID(ZxyToID(19, 1000, 4)))
98+
assert.Equal(t, ZxyToID(18, 1, 500), ParentID(ZxyToID(19, 3, 1000)))
99+
assert.Equal(t, ZxyToID(18, 2, 500), ParentID(ZxyToID(19, 4, 1000)))
95100
}

0 commit comments

Comments
 (0)