108 lines
2.7 KiB
Dart
108 lines
2.7 KiB
Dart
import 'package:aves/model/gpx/gpx_track.dart';
|
|
import 'package:latlong2/latlong.dart';
|
|
import 'package:xml/xml.dart';
|
|
|
|
class GpxParser {
|
|
static GpxTrack parse(
|
|
String xmlContent, {
|
|
String fallbackName = 'GPX track',
|
|
}) {
|
|
final document = XmlDocument.parse(xmlContent);
|
|
|
|
final name = _readTrackName(document) ?? fallbackName;
|
|
final segments = <List<LatLng>>[];
|
|
|
|
// Standard GPX tracks: trk/trkseg/trkpt
|
|
for (final segment in _elementsByLocalName(document, 'trkseg')) {
|
|
final points = _readLatLngElements(
|
|
segment.findElements('trkpt'),
|
|
);
|
|
|
|
if (points.length >= 2) {
|
|
segments.add(points);
|
|
}
|
|
}
|
|
|
|
// GPX routes: rte/rtept
|
|
for (final route in _elementsByLocalName(document, 'rte')) {
|
|
final points = _readLatLngElements(
|
|
route.findElements('rtept'),
|
|
);
|
|
|
|
if (points.length >= 2) {
|
|
segments.add(points);
|
|
}
|
|
}
|
|
|
|
// Fallback: waypoints as a single line only if there are enough.
|
|
if (segments.isEmpty) {
|
|
final waypoints = _readLatLngElements(
|
|
_elementsByLocalName(document, 'wpt'),
|
|
);
|
|
|
|
if (waypoints.length >= 2) {
|
|
segments.add(waypoints);
|
|
}
|
|
}
|
|
|
|
return GpxTrack(
|
|
name: name,
|
|
tracks: segments.toSet(),
|
|
);
|
|
}
|
|
|
|
static List<LatLng> _readLatLngElements(
|
|
Iterable<XmlElement> elements,
|
|
) {
|
|
final points = <LatLng>[];
|
|
|
|
for (final point in elements) {
|
|
final latString = point.getAttribute('lat');
|
|
final lonString = point.getAttribute('lon');
|
|
|
|
if (latString == null || lonString == null) continue;
|
|
|
|
final lat = double.tryParse(latString);
|
|
final lon = double.tryParse(lonString);
|
|
|
|
if (lat == null || lon == null) continue;
|
|
if (!lat.isFinite || !lon.isFinite) continue;
|
|
|
|
points.add(LatLng(lat, lon));
|
|
}
|
|
|
|
return points;
|
|
}
|
|
|
|
static String? _readTrackName(XmlDocument document) {
|
|
for (final track in _elementsByLocalName(document, 'trk')) {
|
|
for (final name in track.findElements('name')) {
|
|
final text = name.innerText.trim();
|
|
if (text.isNotEmpty) return text;
|
|
}
|
|
}
|
|
|
|
for (final route in _elementsByLocalName(document, 'rte')) {
|
|
for (final name in route.findElements('name')) {
|
|
final text = name.innerText.trim();
|
|
if (text.isNotEmpty) return text;
|
|
}
|
|
}
|
|
|
|
for (final name in _elementsByLocalName(document, 'name')) {
|
|
final text = name.innerText.trim();
|
|
if (text.isNotEmpty) return text;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static Iterable<XmlElement> _elementsByLocalName(
|
|
XmlNode node,
|
|
String localName,
|
|
) {
|
|
return node.descendants.whereType<XmlElement>().where(
|
|
(element) => element.name.local == localName,
|
|
);
|
|
}
|
|
}
|