garmin_img 0.0.18
garmin_img: ^0.0.18 copied to clipboard
Pure-Dart reader for unlocked Garmin .img maps. Decodes TRE, RGN, LBL, TYP and DEM subfiles into typed points, polylines, polygons and elevations.
example/garmin_img_example.dart
// Minimal tour of the garmin_img API: open an unlocked Garmin .img, list its
// sub-maps and levels, decode a few features, look up TYP styles, and sample
// the elevation model when the map ships one.
//
// dart run example/garmin_img_example.dart path/to/gmapsupp.img
import 'dart:io';
import 'package:garmin_img/garmin_img.dart';
Future<void> main(List<String> args) async {
if (args.isEmpty) {
stderr
.writeln('usage: dart run example/garmin_img_example.dart <file.img>');
exit(64);
}
// Streams from disk with a block cache; the whole file is NOT read into RAM.
final img = await GarminImg.open(args[0]);
print('sub-maps: ${img.maps.length}');
for (final map in img.maps) {
print(' bounds ${map.bounds}');
for (final level in map.levels) {
print(' $level');
}
}
// Decode everything that overlaps a viewport at the finest level of detail.
final map = img.maps.first;
final finestBpc = map.levels.first.bitsPerCoord;
final query = BoundingBox(
map.bounds.south + (map.bounds.north - map.bounds.south) * 0.45,
map.bounds.west + (map.bounds.east - map.bounds.west) * 0.45,
map.bounds.south + (map.bounds.north - map.bounds.south) * 0.55,
map.bounds.west + (map.bounds.east - map.bounds.west) * 0.55,
);
var points = 0, lines = 0, polygons = 0;
for (final f in map.featuresInBounds(query, onlyBpc: {finestBpc})) {
switch (f.kind) {
case FeatureKind.point:
points++;
// Labels are names for POIs and elevations for contour lines.
if (points <= 5) {
print(' POI 0x${f.type.toRadixString(16)} "${f.label}" '
'at ${f.points.first}');
}
case FeatureKind.polyline:
lines++;
case FeatureKind.polygon:
polygons++;
}
}
print('viewport: $points points, $lines polylines, $polygons polygons');
// Marked hiking routes are extended-type polylines stored densely at ONE
// level, which varies per area — ask the map which level to decode.
final routeBpc = map.routeBpcForBounds(query);
if (routeBpc != null) {
final routes = map.extPolylinesInBounds(query, onlyBpc: {routeBpc});
print('marked routes at bpc $routeBpc: ${routes.length}');
}
// The embedded TYP style tells you how the map wants a type drawn.
print('TYP: ${img.polygonColors.length} polygon colours, '
'${img.lineStyles.length} line styles, ${img.pointIcons.length} icons');
for (final f in map.polylines().take(200)) {
final style = img.lineStyles[GarminImg.typKeyFor(f.type)];
if (style != null && style.hasPattern) {
print(' route-like line 0x${f.type.toRadixString(16)} → $style');
break;
}
}
// Relief: OpenTopoMap-style maps ship a DEM instead of vector contours.
if (map.hasDem) {
final dem = map.dem!;
final centre = LatLng(
(query.south + query.north) / 2,
(query.west + query.east) / 2,
);
print('DEM levels: ${dem.levels.length}, '
'elevation at $centre = ${dem.elevationAt(centre.lat, centre.lng)} m');
}
img.close();
}