Roadbound-Map-Utility/lib/pages/map/vector/mvt_parser.dart

363 lines
9.2 KiB
Dart

import 'dart:convert';
import 'dart:typed_data';
import 'dart:ui';
enum MvtGeometryType { unknown, point, lineString, polygon }
class MvtTile {
const MvtTile({required this.layers});
final Map<String, MvtLayer> layers;
}
class MvtLayer {
const MvtLayer({
required this.name,
required this.extent,
required this.features,
});
final String name;
final int extent;
final List<MvtFeature> features;
}
class MvtFeature {
const MvtFeature({
required this.type,
required this.properties,
required this.geometry,
});
final MvtGeometryType type;
final Map<String, Object?> properties;
final List<List<Offset>> geometry;
}
class MvtParser {
const MvtParser();
MvtTile parse(Uint8List bytes, {Set<String>? layerNames}) {
final reader = _PbfReader(bytes);
final layers = <String, MvtLayer>{};
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 = <String>[];
final values = <Object?>[];
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 = <MvtFeature>[];
for (final raw in rawFeatures) {
final properties = <String, Object?>{};
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 = <int>[];
final tags = <int>[];
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<List<Offset>> _decodeGeometry(List<int> commands, MvtGeometryType type) {
final geometries = <List<Offset>>[];
var cursorX = 0;
var cursorY = 0;
var i = 0;
List<Offset>? 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(<Offset>[point]);
current = null;
} else {
current = <Offset>[point];
geometries.add(current);
}
}
} else if (commandId == 2) {
if (current == null) {
current = <Offset>[];
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<int> tags;
final List<int> 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 utf8.decode(bytes, allowMalformed: true);
}
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<int> readPackedVarints() {
final bytes = readBytes();
final inner = _PbfReader(bytes);
final values = <int>[];
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');
}
}
}