import 'dart:typed_data'; import 'dart:ui'; enum MvtGeometryType { unknown, point, lineString, polygon } class MvtTile { const MvtTile({required this.layers}); final Map layers; } class MvtLayer { const MvtLayer({ required this.name, required this.extent, required this.features, }); final String name; final int extent; final List features; } class MvtFeature { const MvtFeature({ required this.type, required this.properties, required this.geometry, }); final MvtGeometryType type; final Map properties; final List> geometry; } class MvtParser { const MvtParser(); MvtTile parse(Uint8List bytes, {Set? layerNames}) { final reader = _PbfReader(bytes); final layers = {}; while (!reader.isAtEnd) { final tag = reader.readVarint(); final field = tag >> 3; final wire = tag & 0x07; if (field == 3 && wire == 2) { final layerBytes = reader.readBytes(); final layer = _parseLayer(layerBytes); if (layer == null) continue; if (layerNames != null && !layerNames.contains(layer.name)) continue; layers[layer.name] = layer; } else { reader.skipWireType(wire); } } return MvtTile(layers: layers); } MvtLayer? _parseLayer(Uint8List bytes) { final reader = _PbfReader(bytes); String name = ''; var extent = 4096; final rawFeatures = <_RawFeature>[]; final keys = []; final values = []; while (!reader.isAtEnd) { final tag = reader.readVarint(); final field = tag >> 3; final wire = tag & 0x07; switch (field) { case 1: if (wire == 2) { name = reader.readString(); } else { reader.skipWireType(wire); } case 2: if (wire == 2) { rawFeatures.add(_parseRawFeature(reader.readBytes())); } else { reader.skipWireType(wire); } case 3: if (wire == 2) { keys.add(reader.readString()); } else { reader.skipWireType(wire); } case 4: if (wire == 2) { values.add(_parseValue(reader.readBytes())); } else { reader.skipWireType(wire); } case 5: if (wire == 0) { extent = reader.readVarint(); } else { reader.skipWireType(wire); } default: reader.skipWireType(wire); } } if (name.isEmpty) return null; final features = []; for (final raw in rawFeatures) { final properties = {}; for (var i = 0; i + 1 < raw.tags.length; i += 2) { final keyIndex = raw.tags[i]; final valueIndex = raw.tags[i + 1]; if (keyIndex < 0 || keyIndex >= keys.length) continue; if (valueIndex < 0 || valueIndex >= values.length) continue; properties[keys[keyIndex]] = values[valueIndex]; } features.add( MvtFeature( type: _decodeFeatureType(raw.type), properties: properties, geometry: _decodeGeometry(raw.geometry, _decodeFeatureType(raw.type)), ), ); } return MvtLayer(name: name, extent: extent, features: features); } _RawFeature _parseRawFeature(Uint8List bytes) { final reader = _PbfReader(bytes); var type = 0; var geometry = []; final tags = []; while (!reader.isAtEnd) { final tag = reader.readVarint(); final field = tag >> 3; final wire = tag & 0x07; switch (field) { case 2: if (wire == 2) { tags.addAll(reader.readPackedVarints()); } else { reader.skipWireType(wire); } case 3: if (wire == 0) { type = reader.readVarint(); } else { reader.skipWireType(wire); } case 4: if (wire == 2) { geometry = reader.readPackedVarints(); } else { reader.skipWireType(wire); } default: reader.skipWireType(wire); } } return _RawFeature(type: type, tags: tags, geometry: geometry); } Object? _parseValue(Uint8List bytes) { final reader = _PbfReader(bytes); while (!reader.isAtEnd) { final tag = reader.readVarint(); final field = tag >> 3; final wire = tag & 0x07; switch (field) { case 1: if (wire == 2) return reader.readString(); case 2: if (wire == 5) return reader.readFloat32(); case 3: if (wire == 1) return reader.readFloat64(); case 4: if (wire == 0) return reader.readVarint(); case 5: if (wire == 0) return reader.readVarint(); case 6: if (wire == 0) return _decodeZigZag(reader.readVarint()); case 7: if (wire == 0) return reader.readVarint() != 0; } reader.skipWireType(wire); } return null; } MvtGeometryType _decodeFeatureType(int rawType) { switch (rawType) { case 1: return MvtGeometryType.point; case 2: return MvtGeometryType.lineString; case 3: return MvtGeometryType.polygon; default: return MvtGeometryType.unknown; } } List> _decodeGeometry(List commands, MvtGeometryType type) { final geometries = >[]; var cursorX = 0; var cursorY = 0; var i = 0; List? current; while (i < commands.length) { final commandInteger = commands[i++]; final commandId = commandInteger & 0x7; final count = commandInteger >> 3; if (commandId == 1) { for (var c = 0; c < count; c++) { if (i + 1 >= commands.length) break; cursorX += _decodeZigZag(commands[i++]); cursorY += _decodeZigZag(commands[i++]); final point = Offset(cursorX.toDouble(), cursorY.toDouble()); if (type == MvtGeometryType.point) { geometries.add([point]); current = null; } else { current = [point]; geometries.add(current); } } } else if (commandId == 2) { if (current == null) { current = []; geometries.add(current); } for (var c = 0; c < count; c++) { if (i + 1 >= commands.length) break; cursorX += _decodeZigZag(commands[i++]); cursorY += _decodeZigZag(commands[i++]); current.add(Offset(cursorX.toDouble(), cursorY.toDouble())); } } else if (commandId == 7) { if (type == MvtGeometryType.polygon && current != null && current.isNotEmpty && current.first != current.last) { current.add(current.first); } } else { break; } } return geometries.where((path) => path.isNotEmpty).toList(growable: false); } int _decodeZigZag(int n) => (n >> 1) ^ (-(n & 1)); } class _RawFeature { const _RawFeature({ required this.type, required this.tags, required this.geometry, }); final int type; final List tags; final List geometry; } class _PbfReader { _PbfReader(Uint8List bytes) : _bytes = bytes, _data = ByteData.sublistView(bytes); final Uint8List _bytes; final ByteData _data; int _offset = 0; bool get isAtEnd => _offset >= _bytes.length; int readVarint() { var shift = 0; var result = 0; while (true) { if (_offset >= _bytes.length) { throw const FormatException('Unexpected EOF while reading varint'); } final byte = _bytes[_offset++]; result |= (byte & 0x7f) << shift; if ((byte & 0x80) == 0) break; shift += 7; if (shift > 63) { throw const FormatException('Varint too long'); } } return result; } Uint8List readBytes() { final length = readVarint(); if (length < 0 || _offset + length > _bytes.length) { throw const FormatException('Invalid length-delimited field'); } final bytes = Uint8List.sublistView(_bytes, _offset, _offset + length); _offset += length; return bytes; } String readString() { final bytes = readBytes(); return String.fromCharCodes(bytes); } double readFloat32() { final value = _data.getFloat32(_offset, Endian.little); _offset += 4; return value; } double readFloat64() { final value = _data.getFloat64(_offset, Endian.little); _offset += 8; return value; } List readPackedVarints() { final bytes = readBytes(); final inner = _PbfReader(bytes); final values = []; while (!inner.isAtEnd) { values.add(inner.readVarint()); } return values; } void skipWireType(int wireType) { switch (wireType) { case 0: readVarint(); case 1: _offset += 8; case 2: final length = readVarint(); _offset += length; case 5: _offset += 4; default: throw FormatException('Unsupported wire type: $wireType'); } if (_offset > _bytes.length) { throw const FormatException('Unexpected EOF while skipping field'); } } }