package main import ( "errors" "math" ) // The roads of a Mapbox Vector Tile (spec 2.1) in the OpenMapTiles schema: the line features // of the "transportation" layer whose "class" the car draws (roadClasses), scaled to // mapExtent. A minimal protobuf reader, enough for that and nothing more; anything malformed // is an error, never a guess. type roadLine struct { class int points [][2]int } var errBadTile = errors.New("vector tile is malformed") // pbReader walks protobuf fields. type pbReader struct { b []byte i int } func (r *pbReader) done() bool { return r.i >= len(r.b) } func (r *pbReader) varint() (uint64, error) { var v uint64 for shift := uint(0); shift < 64; shift += 7 { if r.i >= len(r.b) { return 0, errBadTile } c := r.b[r.i] r.i++ v |= uint64(c&0x7f) << shift if c < 0x80 { return v, nil } } return 0, errBadTile } // field reads one field's key and, for length-delimited ones, its bytes. func (r *pbReader) field() (num int, wire int, val uint64, data []byte, err error) { key, err := r.varint() if err != nil { return 0, 0, 0, nil, err } num, wire = int(key>>3), int(key&7) switch wire { case 0: val, err = r.varint() case 1: if r.i+8 > len(r.b) { return 0, 0, 0, nil, errBadTile } r.i += 8 case 2: var n uint64 n, err = r.varint() if err == nil { if n > uint64(len(r.b)-r.i) { return 0, 0, 0, nil, errBadTile } data = r.b[r.i : r.i+int(n)] r.i += int(n) } case 5: if r.i+4 > len(r.b) { return 0, 0, 0, nil, errBadTile } r.i += 4 default: return 0, 0, 0, nil, errBadTile } return num, wire, val, data, err } // packed reads a repeated uint32 field, packed or not. func packed(wire int, val uint64, data []byte, into []uint32) ([]uint32, error) { if wire == 0 { return append(into, uint32(val)), nil } if wire != 2 { return nil, errBadTile } r := pbReader{b: data} for !r.done() { v, err := r.varint() if err != nil { return nil, err } into = append(into, uint32(v)) } return into, nil } // decodeRoads returns the drawable roads of the tile. func decodeRoads(tile []byte) ([]roadLine, error) { r := pbReader{b: tile} for !r.done() { num, _, _, data, err := r.field() if err != nil { return nil, err } if num != 3 { // layers continue } lines, isRoads, err := decodeLayer(data) if err != nil { return nil, err } if isRoads { return lines, nil } } return nil, nil // a tile without roads (sea, desert) is an empty one } type rawFeature struct { tags []uint32 geomType uint64 geometry []uint32 } func decodeLayer(layer []byte) ([]roadLine, bool, error) { r := pbReader{b: layer} var name string var keys []string var values []string // only string values matter for "class" var features []rawFeature extent := uint64(4096) for !r.done() { num, wire, val, data, err := r.field() if err != nil { return nil, false, err } switch num { case 1: name = string(data) case 2: f, err := decodeFeature(data) if err != nil { return nil, false, err } features = append(features, f) case 3: keys = append(keys, string(data)) case 4: values = append(values, valueString(data)) case 5: if wire != 0 || val == 0 { return nil, false, errBadTile } extent = val } } if name != "transportation" { return nil, false, nil } scale := float64(mapExtent) / float64(extent) var lines []roadLine for _, f := range features { if f.geomType != 2 { // LINESTRING continue } class, ok := -1, false for i := 0; i+1 < len(f.tags); i += 2 { k, v := int(f.tags[i]), int(f.tags[i+1]) if k >= len(keys) || v >= len(values) { return nil, false, errBadTile } if keys[k] == "class" { class, ok = roadClasses[values[v]] if !ok { class = -1 } } } if class < 0 { continue } parts, err := lineGeometry(f.geometry, scale) if err != nil { return nil, false, err } for _, p := range parts { lines = append(lines, roadLine{class: class, points: p}) } } return lines, true, nil } func decodeFeature(data []byte) (rawFeature, error) { var f rawFeature r := pbReader{b: data} for !r.done() { num, wire, val, d, err := r.field() if err != nil { return f, err } switch num { case 2: if f.tags, err = packed(wire, val, d, f.tags); err != nil { return f, err } case 3: f.geomType = val case 4: if f.geometry, err = packed(wire, val, d, f.geometry); err != nil { return f, err } } } if len(f.tags)%2 != 0 { return f, errBadTile } return f, nil } // valueString is a Value's string_value, "" for any other kind. func valueString(data []byte) string { r := pbReader{b: data} for !r.done() { num, _, _, d, err := r.field() if err != nil { return "" } if num == 1 { return string(d) } } return "" } // lineGeometry follows the MoveTo/LineTo commands of a (multi)line string. func lineGeometry(cmds []uint32, scale float64) ([][][2]int, error) { var parts [][][2]int var cur [][2]int x, y := 0, 0 zig := func(v uint32) int { return int(int32(v>>1) ^ -int32(v&1)) } at := func() [2]int { return [2]int{int(math.Round(float64(x) * scale)), int(math.Round(float64(y) * scale))} } for i := 0; i < len(cmds); { id, count := cmds[i]&7, int(cmds[i]>>3) i++ switch id { case 1, 2: // MoveTo, LineTo if i+2*count > len(cmds) { return nil, errBadTile } for k := 0; k < count; k++ { x += zig(cmds[i]) y += zig(cmds[i+1]) i += 2 if id == 1 { if len(cur) >= 2 { parts = append(parts, cur) } cur = [][2]int{at()} } else { if p := at(); len(cur) == 0 || cur[len(cur)-1] != p { cur = append(cur, p) } } } case 7: // ClosePath, not in a line; ignored default: return nil, errBadTile } } if len(cur) >= 2 { parts = append(parts, cur) } return parts, nil }