From dcfa28afcb8b486e184303c2163ed643de051084 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=AD=99=E5=A8=87?=
Date: Fri, 8 Mar 2024 17:14:50 +0800
Subject: [PATCH] add `Style` support for kml
ref: https://developers.google.com/kml/documentation/kmlreference
---
lib/geoxml.dart | 2 +-
lib/src/kml_reader.dart | 314 +++++++++++++++++++++++--
lib/src/kml_writer.dart | 91 ++++---
lib/src/model/geo_object.dart | 6 +-
lib/src/model/geo_style.dart | 208 ++++++++++++++++
lib/src/model/geoxml.dart | 10 +-
lib/src/model/kml_tag.dart | 26 +-
lib/src/model/polygon.dart | 132 -----------
lib/src/model/rte.dart | 4 +
lib/src/model/trk.dart | 4 +
lib/src/model/wpt.dart | 4 +
test/assets/style_test.kml | 75 ++++++
test/tests/kml_reader_stream_test.dart | 14 ++
13 files changed, 685 insertions(+), 205 deletions(-)
create mode 100644 lib/src/model/geo_style.dart
delete mode 100644 lib/src/model/polygon.dart
create mode 100644 test/assets/style_test.kml
diff --git a/lib/geoxml.dart b/lib/geoxml.dart
index bc1410d..e69f7e8 100644
--- a/lib/geoxml.dart
+++ b/lib/geoxml.dart
@@ -7,6 +7,7 @@ export 'src/kml_writer.dart';
export 'src/model/bounds.dart';
export 'src/model/copyright.dart';
export 'src/model/email.dart';
+export 'src/model/geo_style.dart';
export 'src/model/geoxml.dart';
export 'src/model/link.dart';
export 'src/model/metadata.dart';
@@ -15,4 +16,3 @@ export 'src/model/rte.dart';
export 'src/model/trk.dart';
export 'src/model/trkseg.dart';
export 'src/model/wpt.dart';
-export 'src/model/polygon.dart';
diff --git a/lib/src/kml_reader.dart b/lib/src/kml_reader.dart
index 6ab1030..816ac08 100644
--- a/lib/src/kml_reader.dart
+++ b/lib/src/kml_reader.dart
@@ -5,6 +5,7 @@ import 'package:xml/xml_events.dart';
import 'model/copyright.dart';
import 'model/email.dart';
import 'model/geo_object.dart';
+import 'model/geo_style.dart';
import 'model/geoxml.dart';
import 'model/gpx_tag.dart';
import 'model/kml_tag.dart';
@@ -35,7 +36,7 @@ class KmlReader {
Future _fromIterator(StreamIterator iterator) async {
// ignore: avoid_as
- final gpx = GeoXml();
+ final geoXml = GeoXml();
String? kmlName;
String? desc;
Person? author;
@@ -62,39 +63,42 @@ class KmlReader {
author = await _readPerson(iterator);
break;
case KmlTag.extendedData:
- gpx.metadata = await _parseMetadata(iterator);
+ geoXml.metadata = await _parseMetadata(iterator);
break;
case KmlTag.placemark:
- final item = await _readPlacemark(iterator, val.name);
+ final item = await _readPlacemark(iterator, val.name, geoXml);
if (item is Wpt) {
- gpx.wpts.add(item);
+ geoXml.wpts.add(item);
} else if (item is Rte) {
- gpx.rtes.add(item);
+ geoXml.rtes.add(item);
}
break;
case KmlTag.folder:
- gpx.trks.add(await _readFolder(iterator, val.name));
+ geoXml.trks.add(await _readFolder(iterator, val.name, geoXml));
+ break;
+ case KmlTag.style:
+ geoXml.styles.add(await _readStyle(iterator, val.name));
break;
}
}
}
if (kmlName != null) {
- gpx.metadata ??= Metadata();
- gpx.metadata!.name = kmlName;
+ geoXml.metadata ??= Metadata();
+ geoXml.metadata!.name = kmlName;
}
if (author != null) {
- gpx.metadata ??= Metadata();
- gpx.metadata!.author = author;
+ geoXml.metadata ??= Metadata();
+ geoXml.metadata!.author = author;
}
if (desc != null) {
- gpx.metadata ??= Metadata();
- gpx.metadata!.desc = desc;
+ geoXml.metadata ??= Metadata();
+ geoXml.metadata!.desc = desc;
}
- return gpx;
+ return geoXml;
}
Future _parseMetadata(StreamIterator iterator) async {
@@ -133,9 +137,10 @@ class KmlReader {
}
Future _readPlacemark(
- StreamIterator iterator, String tagName) async {
+ StreamIterator iterator, String tagName, GeoXml geoXml) async {
final item = GeoObject();
final elm = iterator.current;
+ GeoStyle? style;
DateTime? time;
Wpt? ext;
Wpt? wpt;
@@ -183,6 +188,20 @@ class KmlReader {
case KmlTag.gxTrack:
rte = await _readGxTrack(iterator, val.name);
break;
+ case KmlTag.style:
+ style = await _readStyle(iterator, val.name);
+ break;
+ case KmlTag.styleUrl:
+ var styleUrl = await _readString(iterator, val.name);
+ if (styleUrl != null && style == null) {
+ if (styleUrl.startsWith('#')) {
+ styleUrl = styleUrl.substring(1);
+ }
+
+ style = geoXml.styles.firstWhere(
+ (element) => element.id == styleUrl);
+ }
+ break;
}
}
@@ -215,6 +234,10 @@ class KmlReader {
wpt.number = ext.number;
}
+ if (style != null) {
+ wpt.style = style;
+ }
+
return wpt;
} else if (rte is Rte) {
rte.name = item.name;
@@ -233,14 +256,22 @@ class KmlReader {
rte.number = ext.number;
}
+ if (style != null) {
+ rte.style = style;
+ }
+
return rte;
}
+ if (style != null) {
+ item.style = style;
+ }
+
return item;
}
Future _readFolder(
- StreamIterator iterator, String tagName) async {
+ StreamIterator iterator, String tagName, GeoXml geoXml) async {
final trk = Trk();
final elm = iterator.current;
Wpt? ext;
@@ -270,7 +301,7 @@ class KmlReader {
ext = await _readExtended(iterator);
break;
case KmlTag.placemark:
- final item = await _readPlacemark(iterator, val.name);
+ final item = await _readPlacemark(iterator, val.name, geoXml);
if (item is Wpt) {
if (trk.trksegs.isEmpty) {
trk.trksegs.add(Trkseg());
@@ -306,9 +337,9 @@ class KmlReader {
}
Future _readInt(
- StreamIterator iterator, String tagName) async {
+ StreamIterator iterator, String tagName, [int radix=10]) async {
final intString = await _readString(iterator, tagName);
- return intString != null ? int.parse(intString) : null;
+ return intString != null ? int.parse(intString, radix: radix) : null;
}
Future _readDateTime(
@@ -346,6 +377,15 @@ class KmlReader {
return string.trim();
}
+ Future _readEnum(
+ StreamIterator iterator, String tagName, List values) async {
+ final name = await _readString(iterator, tagName);
+ if (name == null) {
+ return null;
+ }
+ return values.firstWhere((e) => e.name == name);
+ }
+
Future _readData(
StreamIterator iterator,
Future Function(StreamIterator iterator, String tagName)
@@ -641,4 +681,242 @@ class KmlReader {
return email;
}
+
+ Future _readStyle(
+ StreamIterator iterator, String tagName) async {
+ final style = GeoStyle();
+ final elm = iterator.current;
+
+ if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) {
+ for (final attribute in elm.attributes) {
+ if (attribute.name == KmlTag.id) {
+ style.id = attribute.value;
+ }
+ }
+
+ while (await iterator.moveNext()) {
+ final val = iterator.current;
+
+ if (val is XmlStartElementEvent) {
+ switch (val.name) {
+ case KmlTag.lineStyle:
+ style.lineStyle = await _readLineStyle(iterator, val.name);
+ break;
+ case KmlTag.polyStyle:
+ style.polyStyle = await _readPolyStyle(iterator, val.name);
+ break;
+ case KmlTag.iconStyle:
+ style.iconStyle = await _readIconStyle(iterator, val.name);
+ break;
+ case KmlTag.labelStyle:
+ style.labelStyle = await _readLabelStyle(iterator, val.name);
+ break;
+ case KmlTag.balloonStyle:
+ style.balloonStyle = await _readBalloonStyle(iterator, val.name);
+ break;
+ }
+ }
+
+ if (val is XmlEndElementEvent && val.name == tagName) {
+ break;
+ }
+ }
+ }
+
+ return style;
+ }
+
+ Future _readLineStyle(
+ StreamIterator iterator, String tagName) async {
+ final lineStyle = LineStyle();
+ final elm = iterator.current;
+ if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) {
+ while (await iterator.moveNext()) {
+ final val = iterator.current;
+ if (val is XmlStartElementEvent) {
+ switch (val.name) {
+ case KmlTag.color:
+ lineStyle.color = await _readInt(iterator, val.name, 16);
+ break;
+ case KmlTag.colorMode:
+ lineStyle.colorMode = await _readEnum(
+ iterator, val.name, ColorMode.values);
+ break;
+ case KmlTag.width:
+ lineStyle.width = await _readDouble(iterator, val.name);
+
+ }
+ }
+ if (val is XmlEndElementEvent && val.name == tagName) {
+ break;
+ }
+ }
+ }
+ return lineStyle;
+ }
+
+ Future _readPolyStyle(
+ StreamIterator iterator, String tagName) async {
+ final polyStyle = PolyStyle();
+ final elm = iterator.current;
+ if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) {
+ while (await iterator.moveNext()) {
+ final val = iterator.current;
+ if (val is XmlStartElementEvent) {
+ switch (val.name) {
+ case KmlTag.color:
+ polyStyle.color = await _readInt(iterator, val.name, 16);
+ break;
+ case KmlTag.colorMode:
+ polyStyle.colorMode = await _readEnum(
+ iterator, val.name, ColorMode.values);
+ break;
+ case KmlTag.fill:
+ polyStyle.fill = await _readInt(iterator, val.name, 16);
+ break;
+ case KmlTag.outline:
+ polyStyle.outline = await _readInt(iterator, val.name, 16);
+ break;
+
+ }
+ }
+ if (val is XmlEndElementEvent && val.name == tagName) {
+ break;
+ }
+ }
+ }
+ return polyStyle;
+ }
+
+ Future _readIconStyle(
+ StreamIterator iterator, String tagName) async {
+ final iconStyle = IconStyle();
+ final elm = iterator.current;
+ if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) {
+ while (await iterator.moveNext()) {
+ final val = iterator.current;
+ if (val is XmlStartElementEvent) {
+ switch (val.name) {
+ case KmlTag.color:
+ iconStyle.color = await _readInt(iterator, val.name, 16);
+ break;
+ case KmlTag.colorMode:
+ iconStyle.colorMode = await _readEnum(
+ iterator, val.name, ColorMode.values);
+ break;
+ case KmlTag.scale:
+ iconStyle.scale = await _readDouble(iterator, val.name);
+ break;
+ case KmlTag.heading:
+ iconStyle.heading = await _readDouble(iterator, val.name);
+ break;
+ case KmlTag.icon:
+ while (await iterator.moveNext()) {
+ final val = iterator.current;
+ if (val is XmlStartElementEvent && val.name == KmlTag.href) {
+ iconStyle.iconUrl = await _readString(iterator, val.name);
+ }
+ if (val is XmlEndElementEvent && val.name == KmlTag.icon) {
+ break;
+ }
+ }
+ break;
+ case KmlTag.hotSpot:
+ for (final attribute in val.attributes) {
+ switch (attribute.name) {
+ case KmlTag.hotSpotX:
+ iconStyle.x = double.tryParse(attribute.value);
+ break;
+ case KmlTag.hotSpotY:
+ iconStyle.y = double.tryParse(attribute.value);
+ break;
+ case KmlTag.xunits:
+ iconStyle.xunit = HotspotUnits.values.firstWhere(
+ (element) => element.name == attribute.value);
+ break;
+ case KmlTag.yunits:
+ iconStyle.yunit = HotspotUnits.values.firstWhere(
+ (element) => element.name == attribute.value);
+ break;
+ }
+ }
+ }
+ }
+ if (val is XmlEndElementEvent && val.name == tagName) {
+ break;
+ }
+ }
+ }
+ return iconStyle;
+ }
+
+ Future _readLabelStyle(
+ StreamIterator iterator, String tagName) async {
+ final labelStyle = LabelStyle();
+ final elm = iterator.current;
+ if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) {
+ while (await iterator.moveNext()) {
+ final val = iterator.current;
+ if (val is XmlStartElementEvent) {
+ switch (val.name) {
+ case KmlTag.color:
+ labelStyle.color = await _readInt(iterator, val.name, 16);
+ break;
+ case KmlTag.colorMode:
+ labelStyle.colorMode = await _readEnum(
+ iterator, val.name, ColorMode.values);
+ break;
+ case KmlTag.scale:
+ labelStyle.scale = await _readDouble(iterator, val.name);
+ break;
+
+ }
+ }
+ if (val is XmlEndElementEvent && val.name == tagName) {
+ break;
+ }
+ }
+ }
+ return labelStyle;
+ }
+
+ Future _readBalloonStyle(
+ StreamIterator iterator, String tagName) async {
+ final balloonStyle = BalloonStyle();
+ final elm = iterator.current;
+ if ((elm is XmlStartElementEvent) && !elm.isSelfClosing) {
+ while (await iterator.moveNext()) {
+ final val = iterator.current;
+ if (val is XmlStartElementEvent) {
+ switch (val.name) {
+ case KmlTag.color:
+ balloonStyle.color = await _readInt(iterator, val.name, 16);
+ break;
+ case KmlTag.colorMode:
+ balloonStyle.colorMode = await _readEnum(
+ iterator, val.name, ColorMode.values);
+ break;
+ case KmlTag.bgColor:
+ balloonStyle.bgColor = await _readInt(iterator, val.name, 16);
+ break;
+ case KmlTag.textColor:
+ balloonStyle.textColor = await _readInt(iterator, val.name, 16);
+ break;
+ case KmlTag.text:
+ balloonStyle.text = await _readString(iterator, val.name) ?? '';
+ break;
+ case KmlTag.displayMode:
+ balloonStyle.show =
+ await _readString(iterator, val.name) == 'default';
+ break;
+
+ }
+ }
+ if (val is XmlEndElementEvent && val.name == tagName) {
+ break;
+ }
+ }
+ }
+ return balloonStyle;
+ }
}
diff --git a/lib/src/kml_writer.dart b/lib/src/kml_writer.dart
index cb07c2a..3ec491c 100644
--- a/lib/src/kml_writer.dart
+++ b/lib/src/kml_writer.dart
@@ -6,7 +6,6 @@ import 'model/gpx_tag.dart';
import 'model/kml_tag.dart';
import 'model/link.dart';
import 'model/metadata.dart';
-import 'model/polygon.dart';
import 'model/rte.dart';
import 'model/trk.dart';
import 'model/wpt.dart';
@@ -58,9 +57,9 @@ class KmlWriter {
_writePoint(builder, KmlTag.placemark, wpt);
}
- for (final polygon in gpx.polygons) {
- _writePolygon(builder, polygon);
- }
+ // for (final polygon in gpx.polygons) {
+ // _writePolygon(builder, polygon);
+ // }
for (final rte in gpx.rtes) {
_writeTrackRoute(builder, rte);
@@ -190,48 +189,48 @@ class KmlWriter {
});
}
- void _writePolygon(XmlBuilder builder, Polygon polygon) {
- builder.element(KmlTag.placemark, nest: () {
- _writeElement(builder, KmlTag.name, polygon.name);
- _writeElement(builder, KmlTag.desc, polygon.desc);
- _writeAtomLinks(builder, polygon.links);
-
- // Style the polygon.
- builder.element(KmlTag.style, nest: () {
- builder.element(KmlTag.linestyle, nest: () {
- _writeElement(builder, KmlTag.color,
- polygon.outlineColor.toRadixString(16));
- _writeElement(builder, KmlTag.width, polygon.outlineWidth);
- });
- builder.element(KmlTag.polystyle, nest: () {
- _writeElement(
- builder, KmlTag.color, polygon.fillColor.toRadixString(16));
- _writeElement(builder, KmlTag.outline, 0);
- });
- });
-
- builder.element(KmlTag.extendedData, nest: () {
- _writeExtendedElement(builder, GpxTag.comment, polygon.cmt);
- _writeExtendedElement(builder, GpxTag.type, polygon.type);
-
- _writeExtendedElement(builder, GpxTag.src, polygon.src);
- _writeExtendedElement(builder, GpxTag.number, polygon.number);
- });
-
- builder.element(KmlTag.polygon, nest: () {
- builder.element(KmlTag.outerBoundaryIs, nest: () {
- builder.element(KmlTag.linearRing, nest: () {
- _writeElement(
- builder,
- KmlTag.coordinates,
- polygon.points
- .map((wpt) => [wpt.lon, wpt.lat].join(','))
- .join('\n'));
- });
- });
- });
- });
- }
+ // void _writePolygon(XmlBuilder builder, Polygon polygon) {
+ // builder.element(KmlTag.placemark, nest: () {
+ // _writeElement(builder, KmlTag.name, polygon.name);
+ // _writeElement(builder, KmlTag.desc, polygon.desc);
+ // _writeAtomLinks(builder, polygon.links);
+ //
+ // // Style the polygon.
+ // builder.element(KmlTag.style, nest: () {
+ // builder.element(KmlTag.lineStyle, nest: () {
+ // _writeElement(builder, KmlTag.color,
+ // polygon.outlineColor.toRadixString(16));
+ // _writeElement(builder, KmlTag.width, polygon.outlineWidth);
+ // });
+ // builder.element(KmlTag.polyStyle, nest: () {
+ // _writeElement(
+ // builder, KmlTag.color, polygon.fillColor.toRadixString(16));
+ // _writeElement(builder, KmlTag.outline, 0);
+ // });
+ // });
+ //
+ // builder.element(KmlTag.extendedData, nest: () {
+ // _writeExtendedElement(builder, GpxTag.comment, polygon.cmt);
+ // _writeExtendedElement(builder, GpxTag.type, polygon.type);
+ //
+ // _writeExtendedElement(builder, GpxTag.src, polygon.src);
+ // _writeExtendedElement(builder, GpxTag.number, polygon.number);
+ // });
+ //
+ // builder.element(KmlTag.polygon, nest: () {
+ // builder.element(KmlTag.outerBoundaryIs, nest: () {
+ // builder.element(KmlTag.linearRing, nest: () {
+ // _writeElement(
+ // builder,
+ // KmlTag.coordinates,
+ // polygon.points
+ // .map((wpt) => [wpt.lon, wpt.lat].join(','))
+ // .join('\n'));
+ // });
+ // });
+ // });
+ // });
+ // }
void _writePoint(XmlBuilder builder, String tagName, Wpt wpt) {
builder.element(tagName, nest: () {
diff --git a/lib/src/model/geo_object.dart b/lib/src/model/geo_object.dart
index abf85bb..885efcb 100644
--- a/lib/src/model/geo_object.dart
+++ b/lib/src/model/geo_object.dart
@@ -1,3 +1,4 @@
+import 'geo_style.dart';
import 'link.dart';
class GeoObject {
@@ -27,6 +28,9 @@ class GeoObject {
/// here.
Map extensions = {};
- // Element tag.
+ /// Element tag.
late String tag;
+
+ /// Kml style
+ GeoStyle? style;
}
diff --git a/lib/src/model/geo_style.dart b/lib/src/model/geo_style.dart
new file mode 100644
index 0000000..1c6d22f
--- /dev/null
+++ b/lib/src/model/geo_style.dart
@@ -0,0 +1,208 @@
+import 'package:quiver/core.dart';
+
+class GeoStyle {
+ String? id;
+ LineStyle? lineStyle;
+ PolyStyle? polyStyle;
+ IconStyle? iconStyle;
+ LabelStyle? labelStyle;
+ BalloonStyle? balloonStyle;
+ // ListStyle? listStyle;
+ GeoStyle({
+ this.id,
+ this.lineStyle,
+ this.polyStyle,
+ this.iconStyle,
+ this.labelStyle,
+ this.balloonStyle,
+ // this.listStyle,
+ });
+}
+
+enum ColorMode {
+ normal('normal'),
+ random('random');
+
+ const ColorMode(this.value);
+
+ final String value;
+}
+
+abstract class ColorStyle {
+ int? color;
+ ColorMode? colorMode;
+}
+
+class LineStyle extends ColorStyle {
+ double? width;
+
+ @override
+ // ignore: type_annotate_public_apis
+ bool operator ==(other) {
+ if (other is LineStyle) {
+ return other.color == super.color &&
+ other.colorMode == super.colorMode &&
+ other.width == width;
+ }
+
+ return false;
+ }
+
+ @override
+ String toString() => "LineStyle[${[width].join(",")}]";
+
+ @override
+ int get hashCode => hashObjects([
+ width
+ ]);
+}
+
+class PolyStyle extends ColorStyle {
+ int? fill;
+ int? outline;
+
+ @override
+ // ignore: type_annotate_public_apis
+ bool operator ==(other) {
+ if (other is PolyStyle) {
+ return other.color == super.color &&
+ other.colorMode == super.colorMode &&
+ other.fill == fill &&
+ other.outline == outline;
+ }
+
+ return false;
+ }
+
+ @override
+ String toString() => "PolyStyle[${[
+ color, colorMode, fill, outline,].join(",")}]";
+
+ @override
+ int get hashCode => hashObjects([
+ color,
+ colorMode,
+ fill,
+ outline,
+ ]);
+}
+
+enum HotspotUnits {
+ /// Fraction
+ fraction('fraction'),
+ /// Pixels offset from left or bottom
+ pixels('pixels'),
+ /// Pixels offset from right or top
+ insetPixels('insetPixels');
+
+ const HotspotUnits(this.value);
+
+ final String value;
+}
+
+class IconStyle extends ColorStyle {
+ String? iconUrl;
+ double? scale;
+ double? heading;
+ num? x;
+ num? y;
+ HotspotUnits? xunit;
+ HotspotUnits? yunit;
+
+ @override
+ // ignore: type_annotate_public_apis
+ bool operator ==(other) {
+ if (other is IconStyle) {
+ return other.color == super.color &&
+ other.colorMode == super.colorMode &&
+ other.iconUrl == iconUrl &&
+ other.scale == scale &&
+ other.heading == heading &&
+ other.x == x &&
+ other.y == y &&
+ other.xunit == xunit &&
+ other.yunit == yunit;
+ }
+
+ return false;
+ }
+
+ @override
+ String toString() => "IconStyle[${[
+ iconUrl, scale, heading, x, y, xunit, yunit,].join(",")}]";
+
+ @override
+ int get hashCode => hashObjects([
+ iconUrl,
+ scale,
+ heading,
+ x,
+ y,
+ xunit,
+ yunit,
+ ]);
+}
+
+class LabelStyle extends ColorStyle {
+ double? scale;
+
+ @override
+ // ignore: type_annotate_public_apis
+ bool operator ==(other) {
+ if (other is LabelStyle) {
+ return other.color == super.color &&
+ other.colorMode == super.colorMode &&
+ other.scale == scale;
+ }
+
+ return false;
+ }
+
+ @override
+ String toString() => "LabelStyle[${[scale].join(",")}]";
+
+ @override
+ int get hashCode => hashObjects([
+ scale,
+ ]);
+}
+
+class BalloonStyle extends ColorStyle {
+ int? bgColor;
+ int? textColor = 0xff000000;
+ String text = '';
+ bool show = true;
+
+ @override
+ // ignore: type_annotate_public_apis
+ bool operator ==(other) {
+ if (other is BalloonStyle) {
+ return other.color == super.color &&
+ other.colorMode == super.colorMode &&
+ other.bgColor == bgColor &&
+ other.textColor == textColor &&
+ other.text == text &&
+ other.show == show;
+ }
+
+ return false;
+ }
+
+ @override
+ String toString() => "BalloonStyle[${[
+ bgColor, textColor, text, show].join(",")}]";
+
+ @override
+ int get hashCode => hashObjects([
+ bgColor,
+ textColor,
+ text,
+ show,
+ ]);
+}
+
+class ListStyle {
+ ListStyle() {
+ throw UnimplementedError();
+ }
+}
diff --git a/lib/src/model/geoxml.dart b/lib/src/model/geoxml.dart
index d5de572..a86f373 100644
--- a/lib/src/model/geoxml.dart
+++ b/lib/src/model/geoxml.dart
@@ -5,8 +5,8 @@ import '../gpx_reader.dart';
import '../gpx_writer.dart';
import '../kml_reader.dart';
import '../kml_writer.dart';
+import 'geo_style.dart';
import 'metadata.dart';
-import 'polygon.dart';
import 'rte.dart';
import 'trk.dart';
import 'wpt.dart';
@@ -35,7 +35,7 @@ class GeoXml {
/// A list of tracks.
List trks = [];
- List polygons = [];
+ List styles = [];
/// You can add extend GPX by adding your own elements from another schema
/// here.
@@ -51,7 +51,7 @@ class GeoXml {
const ListEquality().equals(other.wpts, wpts) &&
const ListEquality().equals(other.rtes, rtes) &&
const ListEquality().equals(other.trks, trks) &&
- const ListEquality().equals(other.polygons, polygons) &&
+ const ListEquality().equals(other.styles, styles) &&
const MapEquality().equals(other.extensions, extensions);
}
@@ -66,7 +66,7 @@ class GeoXml {
wpts,
rtes,
trks,
- polygons,
+ styles,
extensions
].join(",")}]";
@@ -80,7 +80,7 @@ class GeoXml {
...trks,
...rtes,
...wpts,
- ...polygons
+ ...styles
]);
String toGpxString({bool pretty = false}) =>
diff --git a/lib/src/model/kml_tag.dart b/lib/src/model/kml_tag.dart
index 4837254..e9dc692 100644
--- a/lib/src/model/kml_tag.dart
+++ b/lib/src/model/kml_tag.dart
@@ -19,9 +19,31 @@ class KmlTag {
static const linearRing = 'LinearRing';
static const style = 'Style';
- static const polystyle = 'PolyStyle';
- static const linestyle = 'LineStyle';
+ static const styleUrl = 'styleUrl';
+ static const id = 'id';
+ static const styleMap = 'StyleMap';
+ static const polyStyle = 'PolyStyle';
+ static const lineStyle = 'LineStyle';
+ static const iconStyle = 'IconStyle';
+ static const labelStyle = 'LabelStyle';
+ static const balloonStyle = 'BalloonStyle';
static const color = 'color';
+ static const colorMode = 'colorMode';
+ static const bgColor = 'bgColor';
+ static const textColor = 'textColor';
+ static const text = 'text';
+ static const displayMode = 'displayMode';
+ static const scale = 'scale';
+ static const heading = 'heading';
+ static const icon = 'Icon';
+ static const href = 'href';
+ static const hotSpot = 'hotSpot';
+ static const hotSpotX = 'x';
+ static const hotSpotY = 'y';
+ static const xunits = 'xunits';
+ static const yunits = 'yunits';
+ static const fill = 'fill';
+
static const width = 'width';
static const outline = 'outline';
diff --git a/lib/src/model/polygon.dart b/lib/src/model/polygon.dart
deleted file mode 100644
index f598efd..0000000
--- a/lib/src/model/polygon.dart
+++ /dev/null
@@ -1,132 +0,0 @@
-import 'package:collection/collection.dart';
-import 'package:geoxml/src/model/kml_tag.dart';
-import 'package:quiver/core.dart';
-
-import '../tools/color_converter.dart';
-import 'geo_object.dart';
-import 'link.dart';
-import 'trk.dart';
-import 'wpt.dart';
-
-/// Polygon represents a simple polygon -
-/// an ordered list of points describing a shape.
-class Polygon implements GeoObject {
- /// Name of Polygon.
- @override
- String? name;
-
- /// GPS comment for track.
- @override
- String? cmt;
-
- /// User description of track.
- @override
- String? desc;
-
- /// Source of data. Included to give user some idea of reliability and
- /// accuracy of data.
- @override
- String? src;
-
- /// Links to external information about the track.
- @override
- List links;
-
- /// GPS track number.
- @override
- int? number;
-
- /// Type (classification) of track.
- @override
- String? type;
-
- /// You can add extend GPX by adding your own elements from another schema
- /// here.
- @override
- Map extensions;
-
- // Element tag.
- @override
- String tag = KmlTag.polygon;
-
- /// A Track Segment holds a list of Track Points which are logically connected
- /// in order. To represent a single GPS track where GPS reception was lost, or
- /// the GPS receiver was turned off, start a new Track Segment for each
- /// continuous span of track data.
- List points;
-
- int fillColor;
- int outlineColor;
-
- int outlineWidth;
-
- /// Construct a new [Trk] object.
- Polygon(
- {this.name,
- this.cmt,
- this.desc,
- this.src,
- List? links,
- this.number,
- this.type,
- Map? extensions,
- List? points,
- fillColor,
- outlineColor,
- this.outlineWidth = 1})
- : links = links ?? [],
- extensions = extensions ?? {},
- points = points ?? [],
- fillColor = _getIntColor(fillColor),
- outlineColor = _getIntColor(outlineColor);
-
- static int _getIntColor(color) {
- if (color == null) {
- return 0x000000;
- } else {
- switch (color.runtimeType) {
- case num:
- return (color as num).toInt();
- case String:
- return hexStringToInt(expandColorWithAlpha(color as String));
- default:
- return 0x00000000;
- }
- }
- }
-
- @override
- // ignore: type_annotate_public_apis
- bool operator ==(other) {
- if (other is Polygon) {
- return other.name == name &&
- other.cmt == cmt &&
- other.desc == desc &&
- other.src == src &&
- const ListEquality().equals(other.links, links) &&
- other.number == number &&
- other.type == type &&
- const MapEquality().equals(other.extensions, extensions) &&
- const ListEquality().equals(other.points, points);
- }
-
- return false;
- }
-
- @override
- String toString() => "Polygon[${[name, type, extensions, points].join(",")}]";
-
- @override
- int get hashCode => hashObjects([
- name,
- cmt,
- desc,
- src,
- number,
- type,
- ...links,
- ...extensions.keys,
- ...extensions.values,
- ...points
- ]);
-}
diff --git a/lib/src/model/rte.dart b/lib/src/model/rte.dart
index fedbce6..004bc6f 100644
--- a/lib/src/model/rte.dart
+++ b/lib/src/model/rte.dart
@@ -1,4 +1,5 @@
import 'package:collection/collection.dart';
+import 'package:geoxml/src/model/geo_style.dart';
import 'package:quiver/core.dart';
import 'geo_object.dart';
@@ -50,6 +51,9 @@ class Rte implements GeoObject {
/// A list of route points.
List rtepts;
+ @override
+ GeoStyle? style;
+
/// Construct a new [Rte] object.
Rte(
{this.name,
diff --git a/lib/src/model/trk.dart b/lib/src/model/trk.dart
index 72f391d..7fd77bb 100644
--- a/lib/src/model/trk.dart
+++ b/lib/src/model/trk.dart
@@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
import 'package:quiver/core.dart';
import 'geo_object.dart';
+import 'geo_style.dart';
import 'gpx_tag.dart';
import 'link.dart';
import 'trkseg.dart';
@@ -52,6 +53,9 @@ class Trk implements GeoObject {
/// continuous span of track data.
List trksegs;
+ @override
+ GeoStyle? style;
+
/// Construct a new [Trk] object.
Trk(
{this.name,
diff --git a/lib/src/model/wpt.dart b/lib/src/model/wpt.dart
index 611e606..992f92b 100644
--- a/lib/src/model/wpt.dart
+++ b/lib/src/model/wpt.dart
@@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
import 'package:quiver/core.dart';
import 'geo_object.dart';
+import 'geo_style.dart';
import 'gpx_tag.dart';
import 'link.dart';
@@ -85,6 +86,9 @@ class Wpt implements GeoObject {
/// ID of DGPS station used in differential correction.
int? dgpsid;
+ @override
+ GeoStyle? style;
+
/// You can add extend GPX by adding your own elements from another schema
/// here.
@override
diff --git a/test/assets/style_test.kml b/test/assets/style_test.kml
new file mode 100644
index 0000000..775c2de
--- /dev/null
+++ b/test/assets/style_test.kml
@@ -0,0 +1,75 @@
+
+
+
+ Ma an shan
+
+
+
+ IconStyle
+ #randomColorIcon
+
+ -122.36868,37.831145,0
+
+
+
+ Ma sn shan range
+
+
+ 1
+
+
+ 114.45731521,30.51243496,0 114.45449889,30.51281854,0 114.45372105,30.51288324,0 114.45314169,30.51292946,0 114.45195615,30.51302651,0 114.45081890,30.51311432,0 114.45031464,30.51313743,0 114.44967628,30.51313743,0 114.44889307,30.51313743,0 114.44858193,30.51313743,0 114.44766998,30.51309121,0 114.44654882,30.51289249,0 114.44545984,30.51273073,0 114.44430113,30.51257822,0 114.44419384,30.51257360,0 114.44357693,30.51243958,0 114.44304049,30.51232866,0 114.44271863,30.51227782,0 114.44194615,30.51215766,0 114.44088399,30.51186650,0 114.43516552,30.50892714,0 114.43428576,30.50846959,0 114.43385124,30.50828010,0 114.43357766,30.50816455,0 114.43296611,30.50798430,0 114.43251550,30.50790111,0 114.43193614,30.50782254,0 114.43108320,30.50779019,0 114.43031609,30.50783641,0 114.42990303,30.50788725,0 114.42889452,30.51031826,0 114.42871749,30.51087286,0 114.42865312,30.51117326,0 114.42873895,30.51139510,0 114.42888916,30.51160307,0 114.42908764,30.51186650,0 114.42928076,30.51206061,0 114.42948460,30.51224085,0 114.42957580,30.51248117,0 114.42962408,30.51273536,0 114.42964554,30.51298954,0 114.42979574,30.51333615,0 114.42999423,30.51359496,0 114.43019271,30.51376595,0 114.43052530,30.51396468,0 114.43080962,30.51409408,0 114.43102956,30.51416802,0 114.43130314,30.51423272,0 114.43154454,30.51427894,0 114.43197369,30.51495829,0 114.43234921,30.51555445,0 114.43245649,30.51583636,0 114.43245649,30.51607667,0 114.43237066,30.51633546,0 114.43237066,30.51664972,0 114.43252087,30.51723201,0 114.43266034,30.51770800,0 114.43281054,30.51865074,0 114.43284273,30.51942249,0 114.43275690,30.51969976,0 114.43269253,30.52001400,0 114.43267107,30.52038369,0 114.43087399,30.52059627,0 114.43116367,30.52136338,0 114.43188250,30.52142345,0 114.43198442,30.52163140,0 114.43173766,30.52183473,0 114.43111002,30.52188556,0 114.42775190,30.52268039,0 114.42767680,30.52288371,0 114.42791820,30.52331347,0 114.42874968,30.52426078,0 114.42857265,30.52460273,0 114.42826688,30.52583652,0 114.42818642,30.52621081,0 114.42784309,30.52785584,0 114.42676485,30.52903876,0 114.42677021,30.52942228,0 114.42688286,30.52952394,0 114.42807376,30.52956090,0 114.42769825,30.52993056,0 114.42608893,30.53219007,0 114.42610502,30.53232406,0 114.42646980,30.53251351,0 114.42688286,30.53262440,0 114.42744076,30.53267061,0 114.42806840,30.53282309,0 114.42846000,30.53348845,0 114.42930222,30.53485150,0 114.42945778,30.53564160,0 114.42968845,30.53623301,0 114.42987084,30.53633928,0 114.43047702,30.53646865,0 114.43077743,30.53657954,0 114.43123877,30.53659341,0 114.43158746,30.53654720,0 114.43186104,30.53647328,0
+
+
+
+
+
+ route 1
+
+
+ 114.43121195,30.50847883,0 114.43121195,30.50849732,0 114.43123341,30.50849732,0 114.43125486,30.50851580,0 114.43127632,30.50853429,0 114.43129778,30.50853429,0 114.43129778,30.50855278,0 114.43131924,30.50857126,0 114.43134069,30.50858975,0 114.43136215,30.50858975,0 114.43136215,30.50860824,0 114.43138361,30.50862673,0 114.43138361,30.50864521,0 114.43140507,30.50864521,0 114.43140507,30.50866370,0 114.43140507,30.50868219,0 114.43140507,30.50870067,0 114.43140507,30.50871916,0 114.43140507,30.50873765,0 114.43140507,30.50875613,0 114.43142653,30.50877462,0 114.43142653,30.50879311,0 114.43142653,30.50881159,0 114.43144798,30.50881159,0 114.43144798,30.50883008,0 114.43144798,30.50884857,0 114.43144798,30.50886706,0 114.43146944,30.50888554,0 114.43146944,30.50890403,0 114.43149090,30.50890403,0 114.43151236,30.50894100,0 114.43151236,30.50895949,0 114.43151236,30.50897798,0 114.43153381,30.50899646,0 114.43153381,30.50901495,0 114.43153381,30.50903344,0 114.43155527,30.50905192,0 114.43155527,30.50907041,0 114.43155527,30.50908890,0 114.43155527,30.50910738,0 114.43155527,30.50912587,0 114.43155527,30.50914436,0 114.43157673,30.50914436,0 114.43157673,30.50916284,0 114.43157673,30.50918133,0 114.43157673,30.50919982,0 114.43159819,30.50921830,0 114.43159819,30.50923679,0 114.43161964,30.50925528,0 114.43161964,30.50927377,0 114.43161964,30.50931074,0 114.43164110,30.50934771,0 114.43166256,30.50938469,0 114.43168402,30.50940317,0 114.43170547,30.50945863,0 114.43172693,30.50949561,0 114.43174839,30.50953258,0 114.43176985,30.50956955,0 114.43176985,30.50958804,0 114.43179131,30.50960653,0 114.43179131,30.50962501,0 114.43181276,30.50964350,0 114.43181276,30.50966199,0 114.43181276,30.50968047,0 114.43181276,30.50969896,0 114.43183422,30.50971745,0 114.43183422,30.50973593,0 114.43185568,30.50973593,0 114.43185568,30.50977291,0 114.43185568,30.50979139,0 114.43187714,30.50980988,0 114.43187714,30.50982837,0 114.43189859,30.50984685,0 114.43189859,30.50986534,0 114.43189859,30.50988383,0 114.43192005,30.50990231,0 114.43192005,30.50992080,0 114.43192005,30.50995777,0 114.43192005,30.50999475,0 114.43192005,30.51001323,0 114.43194151,30.51001323,0 114.43194151,30.51005021,0 114.43194151,30.51006869,0 114.43194151,30.51010567,0 114.43196297,30.51010567,0 114.43196297,30.51014264,0 114.43198442,30.51016113,0 114.43198442,30.51017961,0 114.43198442,30.51019810,0 114.43198442,30.51021659,0 114.43198442,30.51025356,0 114.43200588,30.51027205,0 114.43202734,30.51029053,0 114.43202734,30.51030902,0 114.43204880,30.51030902,0 114.43204880,30.51032751,0 114.43207026,30.51034599,0 114.43209171,30.51036448,0 114.43211317,30.51036448,0 114.43213463,30.51038297,0 114.43215609,30.51040145,0 114.43217754,30.51040145,0 114.43222046,30.51041994,0 114.43226337,30.51045691,0 114.43234921,30.51047540,0 114.43243504,30.51051237,0 114.43249941,30.51053086,0 114.43252087,30.51054935,0 114.43254232,30.51054935,0 114.43258524,30.51056783,0 114.43262815,30.51056783,0 114.43264961,30.51056783,0 114.43269253,30.51056783,0 114.43273544,30.51056783,0 114.43273544,30.51058632,0 114.43277836,30.51058632,0 114.43279982,30.51058632,0 114.43284273,30.51060481,0 114.43288565,30.51060481,0 114.43290710,30.51060481,0 114.43295002,30.51062329,0 114.43301439,30.51062329,0 114.43307877,30.51062329,0 114.43314314,30.51062329,0 114.43320751,30.51062329,0
+
+
+
+ Balloon
+ #exampleBalloonStyle
+
+ -122.370533,37.823842,0
+
+
+
+
diff --git a/test/tests/kml_reader_stream_test.dart b/test/tests/kml_reader_stream_test.dart
index 8374ba3..fe517a5 100644
--- a/test/tests/kml_reader_stream_test.dart
+++ b/test/tests/kml_reader_stream_test.dart
@@ -54,6 +54,20 @@ void main() {
expect(kml, src);
});
+ test('read style kml', () async {
+ final kml = await KmlReader().fromStream(
+ File('test/assets/style_test.kml').openRead().transform(utf8.decoder));
+
+ expect(kml.styles.length, 2);
+ expect(kml.styles.first.id, equals('randomColorIcon'));
+ expect(kml.styles.first.iconStyle?.x, 0.5);
+ expect(kml.rtes.length, 2);
+ expect(kml.rtes.last.style?.lineStyle?.color, 0xffffff00);
+ expect(kml.rtes.first.style?.polyStyle?.color, 0xfe00ffff);
+ expect(kml.wpts.length, 2);
+ expect(kml.wpts.first.style, equals(kml.styles.first));
+ });
+
test('read large', () async {
final kml = await KmlReader().fromStream(
File('test/assets/large.kml').openRead().transform(utf8.decoder));