MobilApp/lib/core/kml_parser.dart

273 lines
9.3 KiB
Dart
Raw Normal View History

// KML és KMZ → flutter_map objektumok
//
// FONTOS: KML koordináta sorrend: lon,lat,alt
// Flutter LatLng(lat, lon) — tehát fordítva kell olvasni!
//
// KMZ = ZIP archív, benne doc.kml
import 'dart:typed_data';
import 'package:archive/archive.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:terepi_seged/enums/layer_import_source_type.dart';
import 'package:uuid/uuid.dart';
import 'package:xml/xml.dart';
import '../models/imported_layer.dart';
class KmlParser {
// Alapértelmezett stílusok — felülírhatók konstruktorban
final Color defaultPolylineColor;
final double defaultPolylineStroke;
final Color defaultPolygonFillColor;
final Color defaultPolygonBorderColor;
final double defaultPolygonBorderStroke;
final Color defaultMarkerColor;
const KmlParser({
this.defaultPolylineColor = const Color(0xCC1565C0), // kék
this.defaultPolylineStroke = 3.0,
this.defaultPolygonFillColor = const Color(0x4D1565C0),
this.defaultPolygonBorderColor = const Color(0xCC1565C0),
this.defaultPolygonBorderStroke = 1.5,
this.defaultMarkerColor = Colors.red,
});
// ── KMZ (ZIP → KML) ──────────────────────────────────────────────
ImportedLayer parseKmz(Uint8List bytes, String fileName,
{String? id, bool isVisible = true}) {
final archive = ZipDecoder().decodeBytes(bytes);
ArchiveFile? kmlFile = archive.findFile('doc.kml');
kmlFile ??= archive.files.firstWhere(
(f) => f.name.toLowerCase().endsWith('.kml'),
orElse: () => throw Exception('Nem található .kml fájl a KMZ-ben'),
);
final kmlString = String.fromCharCodes(kmlFile.content as List<int>);
return _parse(kmlString, fileName, LayerImportSourceType.kmz,
id: id, isVisible: isVisible);
}
// ── KML (szöveg) ──────────────────────────────────────────────────
ImportedLayer parseKml(String kmlString, String fileName,
{String? id, bool isVisible = true}) =>
_parse(kmlString, fileName, LayerImportSourceType.kml,
id: id, isVisible: isVisible);
// ── Belső parse ───────────────────────────────────────────────────
ImportedLayer _parse(
String kmlString, String fileName, LayerImportSourceType source,
{String? id, bool isVisible = true}) {
final doc = XmlDocument.parse(kmlString);
final markers = <Marker>[];
final polylines = <Polyline>[];
final polygons = <Polygon>[];
for (final pm in doc.findAllElements('Placemark')) {
final style = _style(pm);
final label = pm.findElements('name').firstOrNull?.innerText.trim() ?? '';
// Point
final point = pm.findElements('Point').firstOrNull;
if (point != null) {
final pt = _singleCoord(point);
if (pt != null) {
markers.add(Marker(
point: pt,
width: label.isNotEmpty ? 120 : 24,
height: label.isNotEmpty ? 40 : 24,
alignment: Alignment.bottomCenter,
child: _markerWidget(label, style.lineColor),
));
}
}
// LineString
final line = pm.findElements('LineString').firstOrNull;
if (line != null) {
final pts = _coordList(line);
if (pts.isNotEmpty) {
polylines.add(Polyline(
points: pts,
color: style.lineColor,
strokeWidth: style.lineWidth,
borderColor: style.lineColor.withOpacity(0.3),
borderStrokeWidth: 1.0,
));
}
}
// Polygon
final poly = pm.findElements('Polygon').firstOrNull;
if (poly != null) {
final rings = _polygonRings(poly);
if (rings.isNotEmpty) {
polygons.add(Polygon(
points: rings[0],
holePointsList: rings.length > 1 ? rings.sublist(1) : null,
color: style.fillColor,
borderColor: style.lineColor,
borderStrokeWidth: style.lineWidth,
label: label.isNotEmpty ? label : null,
));
}
}
// MultiGeometry — rekurzív feldolgozás az első egyszerű elemre
final multi = pm.findElements('MultiGeometry').firstOrNull;
if (multi != null) {
for (final ls in multi.findElements('LineString')) {
final pts = _coordList(ls);
if (pts.isNotEmpty) {
polylines.add(Polyline(
points: pts,
color: style.lineColor,
strokeWidth: style.lineWidth,
));
}
}
}
}
return ImportedLayer(
id: id ?? const Uuid().v4(),
isVisible: isVisible,
name: fileName,
sourceType: source,
markers: markers,
polylines: polylines,
polygons: polygons,
importedAt: DateTime.now(),
);
}
// ── Koordináta parserek ───────────────────────────────────────────
LatLng? _singleCoord(XmlElement geom) {
final raw = geom.findElements('coordinates').firstOrNull?.innerText.trim();
if (raw == null) return null;
return _oneCoord(raw.split(RegExp(r'\s+')).first);
}
List<LatLng> _coordList(XmlElement geom) {
final raw = geom.findElements('coordinates').firstOrNull?.innerText.trim();
if (raw == null) return [];
return raw
.split(RegExp(r'\s+'))
.map(_oneCoord)
.whereType<LatLng>()
.toList();
}
List<List<LatLng>> _polygonRings(XmlElement polygon) {
final rings = <List<LatLng>>[];
final outer = polygon
.findElements('outerBoundaryIs')
.firstOrNull
?.findElements('LinearRing')
.firstOrNull;
if (outer != null) rings.add(_coordList(outer));
for (final inner in polygon.findElements('innerBoundaryIs')) {
final ring = inner.findElements('LinearRing').firstOrNull;
if (ring != null) rings.add(_coordList(ring));
}
return rings.where((r) => r.isNotEmpty).toList();
}
/// KML: lon,lat,alt → LatLng(lat, lon)
LatLng? _oneCoord(String s) {
final p = s.split(',');
if (p.length < 2) return null;
try {
return LatLng(
double.parse(p[1].trim()), // lat
double.parse(p[0].trim()), // lon
);
} catch (_) {
return null;
}
}
// ── Stílus kiolvasás ──────────────────────────────────────────────
_Style _style(XmlElement pm) {
final style = pm.findElements('Style').firstOrNull;
if (style == null) return _Style.defaults(this);
Color lineColor = defaultPolylineColor;
double lineWidth = defaultPolylineStroke;
Color fillColor = defaultPolygonFillColor;
final ls = style.findElements('LineStyle').firstOrNull;
if (ls != null) {
final c = ls.findElements('color').firstOrNull?.innerText;
if (c != null) lineColor = _kmlColor(c);
final w = ls.findElements('width').firstOrNull?.innerText;
if (w != null) lineWidth = double.tryParse(w) ?? lineWidth;
}
final ps = style.findElements('PolyStyle').firstOrNull;
if (ps != null) {
final c = ps.findElements('color').firstOrNull?.innerText;
if (c != null) fillColor = _kmlColor(c);
}
return _Style(
lineColor: lineColor, lineWidth: lineWidth, fillColor: fillColor);
}
/// KML szín formátum: aabbggrr → Flutter Color(argb)
Color _kmlColor(String s) {
if (s.length != 8) return defaultPolylineColor;
try {
final a = int.parse(s.substring(0, 2), radix: 16);
final b = int.parse(s.substring(2, 4), radix: 16);
final g = int.parse(s.substring(4, 6), radix: 16);
final r = int.parse(s.substring(6, 8), radix: 16);
return Color.fromARGB(a, r, g, b);
} catch (_) {
return defaultPolylineColor;
}
}
// ── Marker widget ─────────────────────────────────────────────────
Widget _markerWidget(String label, Color color) {
if (label.isEmpty) {
return Icon(Icons.location_pin, color: color, size: 24);
}
return Column(mainAxisSize: MainAxisSize.min, children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
decoration:
BoxDecoration(color: color, borderRadius: BorderRadius.circular(4)),
child: Text(label,
style: const TextStyle(
color: Colors.white, fontSize: 10, fontWeight: FontWeight.w600),
overflow: TextOverflow.ellipsis),
),
Icon(Icons.location_pin, color: color, size: 16),
]);
}
}
class _Style {
final Color lineColor;
final double lineWidth;
final Color fillColor;
const _Style(
{required this.lineColor,
required this.lineWidth,
required this.fillColor});
factory _Style.defaults(KmlParser p) => _Style(
lineColor: p.defaultPolylineColor,
lineWidth: p.defaultPolylineStroke,
fillColor: p.defaultPolygonFillColor,
);
}