diff --git a/lib/src/kml_reader.dart b/lib/src/kml_reader.dart
index 3bb88b4..8dc0dcc 100644
--- a/lib/src/kml_reader.dart
+++ b/lib/src/kml_reader.dart
@@ -771,10 +771,10 @@ class KmlReader {
await _readEnum(iterator, val.name, ColorMode.values);
break;
case KmlTag.fill:
- polyStyle.fill = await _readInt(iterator, val.name, 16);
+ polyStyle.fill = await _readInt(iterator, val.name);
break;
case KmlTag.outline:
- polyStyle.outline = await _readInt(iterator, val.name, 16);
+ polyStyle.outline = await _readInt(iterator, val.name);
break;
}
}
@@ -887,10 +887,12 @@ class KmlReader {
if (val is XmlStartElementEvent) {
switch (val.name) {
case KmlTag.bgColor:
- balloonStyle.bgColor = await _readInt(iterator, val.name, 16);
+ balloonStyle.bgColor = await _readInt(iterator, val.name, 16)
+ ?? balloonStyle.bgColor;
break;
case KmlTag.textColor:
- balloonStyle.textColor = await _readInt(iterator, val.name, 16);
+ balloonStyle.textColor = await _readInt(iterator, val.name, 16)
+ ?? balloonStyle.textColor;
break;
case KmlTag.text:
balloonStyle.text = await _readString(iterator, val.name) ?? '';
diff --git a/lib/src/kml_writer.dart b/lib/src/kml_writer.dart
index a7cd6ad..35f2900 100644
--- a/lib/src/kml_writer.dart
+++ b/lib/src/kml_writer.dart
@@ -1,11 +1,13 @@
import 'package:xml/xml.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';
import 'model/link.dart';
import 'model/metadata.dart';
+import 'model/polygon.dart';
import 'model/rte.dart';
import 'model/trk.dart';
import 'model/wpt.dart';
@@ -53,23 +55,27 @@ class KmlWriter {
_writeMetadata(builder, geoXml.metadata!);
}
+ for (final style in geoXml.styles) {
+ _writeStyle(builder, style);
+ }
+
for (final wpt in geoXml.wpts) {
- _writePoint(builder, KmlTag.placemark, wpt);
+ _writePoint(builder, wpt, geoXml);
}
- // for (final polygon in gpx.polygons) {
- // _writePolygon(builder, polygon);
- // }
+ for (final polygon in geoXml.polygons) {
+ _writePolygon(builder, polygon, geoXml);
+ }
for (final rte in geoXml.rtes) {
- _writeTrackRoute(builder, rte);
+ _writeTrackRoute(builder, rte, geoXml);
}
for (final trk in geoXml.trks) {
if (trk.trksegs
.expand((trkseg) => trkseg.trkpts)
.any((element) => element.time == null)) {
- _writeTrackRoute(builder, trk);
+ _writeTrackRoute(builder, trk, geoXml);
} else {
_writeTrack(builder, trk);
}
@@ -113,12 +119,21 @@ class KmlWriter {
});
}
- void _writeTrackRoute(XmlBuilder builder, GeoObject item) {
+ void _writeTrackRoute(XmlBuilder builder, GeoObject item, GeoXml geoXml) {
builder.element(KmlTag.placemark, nest: () {
_writeElement(builder, GpxTag.name, item.name);
_writeElement(builder, GpxTag.desc, item.desc);
_writeAtomLinks(builder, item.links);
+ if (item.style != null) {
+ if (item.style!.id != null && item.style!.id!.isNotEmpty
+ && geoXml.styles.contains(item.style)) {
+ _writeElement(builder, KmlTag.styleUrl, '#${item.style!.id}');
+ } else {
+ _writeStyle(builder, item.style!);
+ }
+ }
+
builder.element(KmlTag.extendedData, nest: () {
_writeExtendedElement(builder, GpxTag.comment, item.cmt);
_writeExtendedElement(builder, GpxTag.type, item.type);
@@ -189,57 +204,136 @@ 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 _writePoint(XmlBuilder builder, String tagName, Wpt wpt) {
- builder.element(tagName, nest: () {
+ void _writePolygon(XmlBuilder builder, Polygon polygon, GeoXml geoXml) {
+ builder.element(KmlTag.placemark, nest: () {
+ _writeElement(builder, KmlTag.name, polygon.name);
+ _writeElement(builder, KmlTag.desc, polygon.desc);
+ _writeAtomLinks(builder, polygon.links);
+
+ // Style the polygon.
+ if (polygon.style != null) {
+ if (polygon.style!.id != null && polygon.style!.id!.isNotEmpty
+ && geoXml.styles.contains(polygon.style)) {
+ _writeElement(builder, KmlTag.styleUrl, '#${polygon.style!.id}');
+ } else {
+ _writeStyle(builder, polygon.style!);
+ }
+ }
+
+ 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 _writeStyle(XmlBuilder builder, GeoStyle style) {
+ builder.element(KmlTag.style,
+ attributes: style.id != null ? {KmlTag.id: style.id!} : {}, nest: () {
+ if (style.lineStyle != null) {
+ builder.element(KmlTag.lineStyle, nest: () {
+ _writeColorStyleElements(builder, style.lineStyle);
+ _writeElement(builder, KmlTag.width, style.lineStyle?.width);
+ });
+ }
+
+ if (style.polyStyle != null) {
+ builder.element(KmlTag.polyStyle, nest: () {
+ _writeColorStyleElements(builder, style.polyStyle);
+ _writeElement(builder, KmlTag.fill, style.polyStyle?.fill);
+ _writeElement(builder, KmlTag.outline, style.polyStyle?.outline);
+ });
+ }
+
+ if (style.iconStyle != null) {
+ builder.element(KmlTag.iconStyle, nest: () {
+ _writeColorStyleElements(builder, style.iconStyle);
+ if (style.iconStyle?.iconUrl != null) {
+ builder.element(KmlTag.icon, nest: () {
+ _writeElement(builder, KmlTag.href, style.iconStyle?.iconUrl);
+ });
+ }
+ _writeElement(builder, KmlTag.scale, style.iconStyle?.scale);
+ _writeElement(builder, KmlTag.heading, style.iconStyle?.heading);
+ if (style.iconStyle?.x != null &&
+ style.iconStyle?.y != null &&
+ style.iconStyle?.xunit != null &&
+ style.iconStyle?.yunit != null){
+ builder.element(KmlTag.hotSpot, attributes: {
+ KmlTag.hotSpotX: style.iconStyle!.x!.toString(),
+ KmlTag.hotSpotY: style.iconStyle!.y!.toString(),
+ KmlTag.xunits: style.iconStyle!.xunit!.name,
+ KmlTag.yunits: style.iconStyle!.yunit!.name,
+ });
+ }
+ });
+ }
+
+ if (style.labelStyle != null) {
+ builder.element(KmlTag.labelStyle, nest: () {
+ _writeColorStyleElements(builder, style.labelStyle);
+ _writeElement(builder, KmlTag.scale, style.labelStyle?.scale);
+ });
+ }
+
+ if (style.balloonStyle != null) {
+ builder.element(KmlTag.balloonStyle, nest: () {
+ _writeElement(builder, KmlTag.bgColor,
+ style.balloonStyle!.bgColor.toRadixString(16));
+ _writeElement(builder, KmlTag.textColor,
+ style.balloonStyle!.textColor.toRadixString(16));
+ _writeElement(builder, KmlTag.text,
+ style.balloonStyle!.text);
+ _writeElement(builder, KmlTag.displayMode,
+ style.balloonStyle!.show ? 'default' : 'hide');
+ });
+ }
+ });
+ }
+
+ void _writeColorStyleElements(XmlBuilder builder, ColorStyle? colorStyle) {
+ if (colorStyle == null) {
+ return;
+ }
+ _writeElement(builder,
+ KmlTag.color, colorStyle.color?.toRadixString(16));
+ _writeElement(builder,
+ KmlTag.colorMode, colorStyle.colorMode?.name);
+ }
+
+ void _writePoint(XmlBuilder builder, Wpt wpt, GeoXml geoXml) {
+ builder.element(KmlTag.placemark, nest: () {
_writeElement(builder, KmlTag.name, wpt.name);
_writeElement(builder, KmlTag.desc, wpt.desc);
_writeElementWithTime(builder, wpt.time);
_writeAtomLinks(builder, wpt.links);
+
+ if (wpt.style != null) {
+ if (wpt.style!.id != null && wpt.style!.id!.isNotEmpty
+ && geoXml.styles.contains(wpt.style)) {
+ _writeElement(builder, KmlTag.styleUrl, '#${wpt.style!.id}');
+ } else {
+ _writeStyle(builder, wpt.style!);
+ }
+ }
builder.element(KmlTag.extendedData, nest: () {
_writeExtendedElement(builder, GpxTag.magVar, wpt.magvar);
diff --git a/lib/src/model/geo_style.dart b/lib/src/model/geo_style.dart
index bfbeaff..71b98cf 100644
--- a/lib/src/model/geo_style.dart
+++ b/lib/src/model/geo_style.dart
@@ -294,10 +294,10 @@ class BalloonStyle {
/// overlay, you would specify the following: 7fff0000,
/// where alpha=0x7f, blue=0xff, green=0x00, and red=0x00. The default is
/// opaque white (ffffffff).
- int? bgColor;
+ int bgColor = 0xffffffff;
/// Foreground color for text. The default is black (ff000000).
- int? textColor = 0xff000000;
+ int textColor = 0xff000000;
/// Text displayed in the balloon. If no text is specified, Google Earth
/// draws the default balloon (with the Feature in boldface, the
diff --git a/lib/src/model/geoxml.dart b/lib/src/model/geoxml.dart
index a86f373..a65fc4f 100644
--- a/lib/src/model/geoxml.dart
+++ b/lib/src/model/geoxml.dart
@@ -7,6 +7,7 @@ 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,6 +36,8 @@ 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
@@ -51,6 +54,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,6 +70,7 @@ class GeoXml {
wpts,
rtes,
trks,
+ polygons,
styles,
extensions
].join(",")}]";
@@ -80,6 +85,7 @@ class GeoXml {
...trks,
...rtes,
...wpts,
+ ...polygons,
...styles
]);
diff --git a/lib/src/model/polygon.dart b/lib/src/model/polygon.dart
new file mode 100644
index 0000000..1fc765a
--- /dev/null
+++ b/lib/src/model/polygon.dart
@@ -0,0 +1,111 @@
+import 'package:collection/collection.dart';
+import 'package:geoxml/src/model/geo_style.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;
+
+ @override
+ GeoStyle? style;
+
+ /// Construct a new [Trk] object.
+ Polygon(
+ {this.name,
+ this.cmt,
+ this.desc,
+ this.src,
+ List? links,
+ this.number,
+ this.type,
+ Map? extensions,
+ List? points})
+ : links = links ?? [],
+ extensions = extensions ?? {},
+ points = points ?? [];
+
+ @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
+ ]);
+}
\ No newline at end of file
diff --git a/test/tests/kml_writer_test.dart b/test/tests/kml_writer_test.dart
index 7be158e..144ae38 100644
--- a/test/tests/kml_writer_test.dart
+++ b/test/tests/kml_writer_test.dart
@@ -64,6 +64,15 @@ void main() {
xml);
});
+ // test('write styled kml', () async {
+ // final kmlString = await File('test/assets/style_test.kml').readAsString();
+ // final kml = await GeoXml.fromKmlString(kmlString);
+ // final kmlOutput = KmlWriter().asString(kml, pretty: true);
+ // print(kmlOutput);
+ //
+ // expectXml(kmlOutput, kmlString);
+ // });
+
test('write large kml', () async {
final gpx = await GpxReader()
.fromString(await File('test/assets/large.gpx').readAsString());