mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-10 03:36:38 +00:00
feat(ui-lvgl): VT3 vector tiles, delta varint points
Points as zigzag varint deltas, a byte length per feature to skip it, natural land cover simplified harder: the Tatra sample goes from 4.4 MB to 2.3 MB, panning at z14 from ~63 to ~45 ms per tile on the L2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
// Vector map (spike): tiles from tools/maps/osm_vector.py under VECTOR_ROOT,
|
||||
// {dz}/{x}/{y}.vt at data zooms 10, 12 and 14, drawn here into the 256x256 RGB565
|
||||
// {dz}/{x}/{y}.vt (format VT3) at data zooms 10, 12 and 14, drawn here into the 256x256 RGB565
|
||||
// buffer the map asks for -- polygons by scanline (even-odd), lines as quads
|
||||
// with round joins. No anti-aliasing, no labels yet. Where there is no vector
|
||||
// data (or below zoom 10) the raster provider draws instead, so the two mix.
|
||||
@@ -61,6 +61,8 @@ private:
|
||||
const uint8_t* _data = nullptr; // the data tile being drawn
|
||||
size_t _data_len = 0;
|
||||
int _ox = 0, _oy = 0, _k = 0, _z = 0;
|
||||
const uint8_t* _end = nullptr; // the feature's parts end here
|
||||
int16_t _bx = 0, _by = 0; // its bbox corner: where point deltas start
|
||||
uint16_t* _out = nullptr;
|
||||
uint32_t _last_ms = 0;
|
||||
|
||||
@@ -73,6 +75,18 @@ private:
|
||||
int32_t _xs[ACTIVE];
|
||||
uint32_t _t_load = 0, _t_fill = 0, _t_line = 0; // us, last tile
|
||||
|
||||
// Unsigned LEB128 varint / zigzag-signed (the tile format, osm_vector.py).
|
||||
static inline uint32_t varint(const uint8_t*& p, const uint8_t* end) {
|
||||
uint32_t v = 0;
|
||||
for (int s = 0; p < end && s < 35; s += 7) {
|
||||
uint8_t b = *p++;
|
||||
v |= (uint32_t)(b & 0x7F) << s;
|
||||
if (!(b & 0x80)) break;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
static inline int32_t zigzag(uint32_t v) { return (int32_t)(v >> 1) ^ -(int32_t)(v & 1); }
|
||||
|
||||
static uint16_t rgb(uint32_t c) { return (uint16_t)(((c >> 8) & 0xF800) | ((c >> 5) & 0x07E0) | ((c >> 3) & 0x1F)); }
|
||||
|
||||
// The data tiles last read (PSRAM), least recently used replaced: the map
|
||||
@@ -109,7 +123,7 @@ private:
|
||||
}
|
||||
d->len = readFast(f, d->buf, sz) ? sz : 0;
|
||||
fclose(f);
|
||||
d->ok = d->len == (size_t)sz && memcmp(d->buf, "VT2", 3) == 0;
|
||||
d->ok = d->len == (size_t)sz && memcmp(d->buf, "VT3", 3) == 0;
|
||||
_data = d->buf;
|
||||
_data_len = d->len;
|
||||
return d->ok;
|
||||
@@ -165,13 +179,12 @@ private:
|
||||
memcpy(&col, p + 2, 2);
|
||||
memcpy(bb, p + 4, 8);
|
||||
p += 12;
|
||||
uint32_t len = varint(p, end);
|
||||
const uint8_t* parts = p;
|
||||
for (int i = 0; i < nparts && p + 2 <= end; i++) { // skip to the next feature
|
||||
uint16_t n;
|
||||
memcpy(&n, p, 2);
|
||||
p += 2 + 4 * n;
|
||||
}
|
||||
if (p > end) return;
|
||||
if (len > (uint32_t)(end - p)) return;
|
||||
p += len; // the next feature
|
||||
_end = p;
|
||||
_bx = bb[0]; _by = bb[1];
|
||||
bool road = cls >= L_SERVICE && cls <= L_TRUNK;
|
||||
bool want = pass == 0 ? (cls < L_SERVICE) : pass == 3 ? cls == L_ROUTE : road;
|
||||
if (!want) continue;
|
||||
@@ -268,19 +281,17 @@ private:
|
||||
// Points of a part, in 1/16 px, closer than half a pixel to the last one
|
||||
// dropped (the data is detailed enough for zoom 18).
|
||||
template <typename F> void forPoints(const uint8_t*& p, bool keep_last, F fn) {
|
||||
uint16_t n;
|
||||
memcpy(&n, p, 2);
|
||||
p += 2;
|
||||
uint32_t n = varint(p, _end);
|
||||
int32_t lx = INT32_MIN, ly = 0;
|
||||
for (int j = 0; j < n; j++) {
|
||||
int16_t ux, uy;
|
||||
memcpy(&ux, p + 4 * j, 2); memcpy(&uy, p + 4 * j + 2, 2);
|
||||
int32_t x = sx(ux), y = sy(uy);
|
||||
int32_t ux = _bx, uy = _by;
|
||||
for (uint32_t j = 0; j < n && p < _end; j++) {
|
||||
ux += zigzag(varint(p, _end));
|
||||
uy += zigzag(varint(p, _end));
|
||||
int32_t x = (ux - _ox) << _k, y = (uy - _oy) << _k;
|
||||
if (lx != INT32_MIN && abs(x - lx) + abs(y - ly) < 8 && !(keep_last && j == n - 1)) continue;
|
||||
fn(x, y, lx == INT32_MIN);
|
||||
lx = x; ly = y;
|
||||
}
|
||||
p += 4 * n;
|
||||
}
|
||||
|
||||
void fillFeature(const uint8_t* p, int nparts, uint16_t col) {
|
||||
|
||||
@@ -12,12 +12,14 @@ Three data zooms: 10 (drawn at z10-11), 12 (z12-13), 14 (z14-18); the device pic
|
||||
data tile covering the tile it draws and scales it. Copy the output folder to
|
||||
the card as /sdcard/vmap.
|
||||
|
||||
Tile format 'VT2' (little-endian):
|
||||
'V' 'T' '2' dz:u8 count:u16
|
||||
Tile format 'VT3' (little-endian):
|
||||
'V' 'T' '3' dz:u8 count:u16
|
||||
count x feature, in drawing order:
|
||||
cls:u8 nparts:u8 colour:u16 (RGB565, 0 = the class's own)
|
||||
bbox: x0 y0 x1 y1 (i16) -- the device skips what's off the tile unread
|
||||
nparts x (npts:u16, npts x (x:i16, y:i16))
|
||||
len: varint -- bytes of the parts that follow (to skip them)
|
||||
nparts x (npts: varint, npts x (dx, dy: zigzag varint))
|
||||
Points are deltas from the previous one; a part's first from (x0, y0).
|
||||
Coordinates: 0..4096 across the data tile, a little past its edges.
|
||||
Polygons: rings, even-odd. Lines: polylines.
|
||||
|
||||
@@ -59,12 +61,30 @@ HIGHWAY = {
|
||||
# Lowest data zoom a class goes in (smaller ones would be clutter / weight).
|
||||
MIN_DZ = {P_BUILDING: 14, L_SERVICE: 14, L_PATH: 12, L_TRACK: 12, L_STREAM: 12, L_MINOR: 12}
|
||||
|
||||
# Natural land cover (not roads, buildings, water): its edges are vague
|
||||
# anyway, so simplified harder -- most of the points are here.
|
||||
NATURAL = (P_MEADOW, P_SCRUB, P_FOREST, P_ROCK)
|
||||
NATURAL_TOL = 3
|
||||
|
||||
WAYMARK = { # osmc:symbol / colour -> RGB888
|
||||
'red': 0xE0302A, 'blue': 0x2A5FE0, 'green': 0x2EA043, 'yellow': 0xE8C20E, 'black': 0x202020,
|
||||
'orange': 0xF08A1C, 'purple': 0x9040C0, 'white': 0xF0F0F0, 'brown': 0x8B5A2B,
|
||||
}
|
||||
|
||||
|
||||
def varint(v):
|
||||
out = bytearray()
|
||||
while v >= 0x80:
|
||||
out.append((v & 0x7F) | 0x80)
|
||||
v >>= 7
|
||||
out.append(v)
|
||||
return out
|
||||
|
||||
|
||||
def zigzag(v):
|
||||
return (v << 1) if v >= 0 else ((-v << 1) - 1)
|
||||
|
||||
|
||||
def rgb565(c):
|
||||
return ((c >> 8) & 0xF800) | ((c >> 5) & 0x07E0) | ((c >> 3) & 0x001F)
|
||||
|
||||
@@ -288,11 +308,12 @@ def main():
|
||||
total_bytes = total_tiles = 0
|
||||
for dz in DATA_ZOOMS:
|
||||
n = 1 << dz
|
||||
tol = 1.0 if dz == DATA_ZOOMS[-1] else 4.0 # half a pixel at the deepest zoom drawn from it
|
||||
base_tol = 1.0 if dz == DATA_ZOOMS[-1] else 4.0 # half a pixel at the deepest zoom drawn from it
|
||||
tiles = defaultdict(list)
|
||||
for cls, col, kind, parts in feats:
|
||||
if MIN_DZ.get(cls, 0) > dz:
|
||||
continue
|
||||
tol = base_tol * (NATURAL_TOL if cls in NATURAL else 1)
|
||||
allp = [p for part in parts for p in part]
|
||||
if not allp:
|
||||
continue
|
||||
@@ -318,7 +339,7 @@ def main():
|
||||
tiles[(tx, ty)].append((cls, col, out))
|
||||
for (tx, ty), fl in tiles.items():
|
||||
fl.sort(key=lambda f: f[0])
|
||||
buf = bytearray(b'VT2' + bytes([dz]) + struct.pack('<H', 0))
|
||||
buf = bytearray(b'VT3' + bytes([dz]) + struct.pack('<H', 0))
|
||||
count = 0
|
||||
for cls, col, parts in fl:
|
||||
for i in range(0, len(parts), 255): # nparts is a byte
|
||||
@@ -332,12 +353,17 @@ def main():
|
||||
q.append(p)
|
||||
qs.append(q)
|
||||
allq = [p for q in qs for p in q]
|
||||
buf += struct.pack('<BBHhhhh', cls, len(chunk), col, min(p[0] for p in allq), min(p[1] for p in allq),
|
||||
max(p[0] for p in allq), max(p[1] for p in allq))
|
||||
bx0, by0 = min(p[0] for p in allq), min(p[1] for p in allq)
|
||||
body = bytearray()
|
||||
for q in qs:
|
||||
buf += struct.pack('<H', len(q))
|
||||
body += varint(len(q))
|
||||
px, py = bx0, by0
|
||||
for x, y in q:
|
||||
buf += struct.pack('<hh', x, y)
|
||||
body += varint(zigzag(x - px)) + varint(zigzag(y - py))
|
||||
px, py = x, y
|
||||
buf += struct.pack('<BBHhhhh', cls, len(chunk), col, bx0, by0,
|
||||
max(p[0] for p in allq), max(p[1] for p in allq))
|
||||
buf += varint(len(body)) + body
|
||||
count += 1
|
||||
struct.pack_into('<H', buf, 4, min(count, 65535))
|
||||
d = os.path.join(a.out, str(dz), str(tx))
|
||||
|
||||
Reference in New Issue
Block a user