363 lines
9.2 KiB
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');
|
|
}
|
|
}
|
|
}
|