diff --git a/examples/companion_radio/ui-lvgl/map/VectorTileProvider.h b/examples/companion_radio/ui-lvgl/map/VectorTileProvider.h index b020d349..81e1c3e1 100644 --- a/examples/companion_radio/ui-lvgl/map/VectorTileProvider.h +++ b/examples/companion_radio/ui-lvgl/map/VectorTileProvider.h @@ -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 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) { diff --git a/tools/maps/osm_vector.py b/tools/maps/osm_vector.py index ed364d2a..77858112 100644 --- a/tools/maps/osm_vector.py +++ b/tools/maps/osm_vector.py @@ -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('