273 lines
9.3 KiB
Dart
273 lines
9.3 KiB
Dart
// 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,
|
|
);
|
|
}
|