geo_osm_pbf 1.0.0
geo_osm_pbf: ^1.0.0 copied to clipboard
Streaming OpenStreetMap `.osm.pbf` reader and extract downloader in pure Dart. Hand-rolled protobuf decoding with no dependencies, delivering nodes, tagged nodes, ways and relations with their full tags.
example/geo_osm_pbf_example.dart
import 'dart:io';
import 'package:geo_osm_pbf/geo_osm_pbf.dart';
/// Summarises what an `.osm.pbf` extract contains.
///
/// With no arguments it downloads a small region and reads that. With a path
/// it reads the file you already have, which is the usual way to try this
/// against a real extract without waiting for a download.
///
/// ```sh
/// dart run example/geo_osm_pbf_example.dart
/// dart run example/geo_osm_pbf_example.dart ./maps/santa-catarina.osm.pbf
/// dart run example/geo_osm_pbf_example.dart --region europe/monaco
/// ```
Future<void> main(List<String> args) async {
final path = await _resolveInput(args);
if (path == null) return;
const parser = OsmPbfParser();
// Reading the header costs one blob, not a scan of the file.
final header = await parser.readHeader(path);
stdout.writeln('File: $path');
if (header != null) {
stdout
..writeln(' written by ${header.writingProgram ?? 'unknown'}')
..writeln(' required ${header.requiredFeatures.join(', ')}')
..writeln(' bounding box ${header.bbox ?? 'not declared'}');
if (header.unsupportedFeatures.isNotEmpty) {
stdout.writeln(
' WARNING: unsupported features ${header.unsupportedFeatures}',
);
}
}
var nodes = 0;
var taggedNodes = 0;
var ways = 0;
var relations = 0;
var multipolygons = 0;
final highways = <String, int>{};
final places = <String, int>{};
final watch = Stopwatch()..start();
await parser.parse(
path,
onNode: (_) => nodes++,
onTaggedNode: (node) {
taggedNodes++;
final place = node.tags['place'];
if (place != null) places[place] = (places[place] ?? 0) + 1;
},
onWay: (way) {
ways++;
final highway = way.tags['highway'];
if (highway != null) highways[highway] = (highways[highway] ?? 0) + 1;
},
onRelation: (relation) {
relations++;
if (relation.isMultipolygon) multipolygons++;
},
);
watch.stop();
stdout
..writeln('')
..writeln(' nodes $nodes')
..writeln(' tagged nodes $taggedNodes')
..writeln(' ways $ways')
..writeln(' relations $relations ($multipolygons multipolygon)')
..writeln(' parsed in ${watch.elapsedMilliseconds} ms')
..writeln('')
..writeln(' top highway classes:');
_printTop(highways, 8);
if (places.isNotEmpty) {
stdout.writeln(' place types:');
_printTop(places, 5);
}
}
/// Returns the `.osm.pbf` to read, downloading one when none was given.
Future<String?> _resolveInput(List<String> args) async {
final positional = args.where((a) => !a.startsWith('--')).toList();
if (positional.isNotEmpty && args.first != '--region') {
final path = positional.first;
if (!File(path).existsSync()) {
stderr.writeln('No such file: $path');
exitCode = 1;
return null;
}
return path;
}
final regionIndex = args.indexOf('--region');
final region = regionIndex >= 0 && regionIndex + 1 < args.length
? args[regionIndex + 1]
: 'europe/monaco';
stdout.writeln('Downloading $region ...');
final downloader = OsmDownloader(outputDirectory: Directory('./osm-cache'));
try {
return await downloader.downloadRegion(
region: region,
onProgress: (received, total, url) {
final mb = (received / 1e6).toStringAsFixed(1);
stdout.write('\r $mb MB');
},
);
} finally {
downloader.close();
stdout.writeln('');
}
}
void _printTop(Map<String, int> counts, int limit) {
final sorted = counts.entries.toList()
..sort((a, b) => b.value.compareTo(a.value));
for (final e in sorted.take(limit)) {
stdout.writeln(' ${e.key.padRight(18)} ${e.value}');
}
}