FSFlightLogger/FlightLoggerLib/Concrete/KMLFlightLogger.cs

242 lines
7.2 KiB
C#

using System;
using System.IO;
using System.Threading.Tasks;
using SharpKml.Base;
using SharpKml.Dom;
namespace FlightLoggerLib.Concrete
{
public class KMLFlightLogger : IFlightLogger
{
private readonly string path;
private string filename;
private readonly System.TimeSpan flushInterval;
private Document output;
private Folder rootFolder;
private LineString positionPath;
private DateTime lastFlush = DateTime.MinValue;
private Vector lastPosition;
private float lastSpeed;
private DateTime lastPointDate = DateTime.MinValue;
public KMLFlightLogger(string path, System.TimeSpan flushInterval)
{
this.path = path;
this.flushInterval = flushInterval;
}
public async Task NewLog()
{
if (output == null)
return;
CheckEndPoint();
await Flush();
output = null;
}
public async ValueTask DisposeAsync()
{
if (output == null)
return;
CheckEndPoint();
await Flush();
}
protected void CheckEndPoint()
{
// TODO replace last "full stop" point if they match
if (lastPosition != null)
{
AddPoint(lastPosition, "End", "end");
lastPosition = null;
}
}
protected async Task AutoFlush()
{
var now = DateTime.Now;
var diff = now - lastFlush;
if (diff < flushInterval)
return;
await Flush();
lastFlush = now;
}
protected async Task Flush()
{
var serializer = new Serializer();
serializer.Serialize(output);
using (var writer = new StreamWriter(filename))
{
await writer.WriteAsync(serializer.Xml);
}
}
protected void EnsureOutput(DateTime eventTime)
{
if (output != null)
return;
var dateString = eventTime.ToString("yyyy-MM-dd HH.mm.ss");
filename = Path.Combine(path, dateString + ".kml");
// Create folder
rootFolder = new Folder
{
Name = dateString,
Open = true
};
// Create flight path line and placemark
positionPath = new LineString
{
Tessellate = false,
AltitudeMode = AltitudeMode.Absolute,
Coordinates = new CoordinateCollection()
};
var positionPlacemark = new Placemark
{
Name = "Flight path",
StyleUrl = new Uri("#flightpath", UriKind.Relative),
Geometry = positionPath
};
rootFolder.AddFeature(positionPlacemark);
output = new Document();
AddFlightPathStyleMap();
var paddleHotspot = new Hotspot { X = 31, XUnits = Unit.Pixel, Y = 1, YUnits = Unit.Pixel };
AddIconStyleMap("start", 1.1, "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png", "http://maps.google.com/mapfiles/kml/paddle/grn-circle-lv.png", paddleHotspot);
AddIconStyleMap("end", 1.1, "http://maps.google.com/mapfiles/kml/paddle/red-square.png", "http://maps.google.com/mapfiles/kml/paddle/red-square-lv.png", paddleHotspot);
AddIconStyleMap("fullstop", 1.1, "http://maps.google.com/mapfiles/kml/shapes/placemark_circle.png", null, paddleHotspot);
output.AddFeature(rootFolder);
}
protected void AddFlightPathStyleMap()
{
var styleMap = new StyleMapCollection { Id = "flightpath" };
styleMap.Add(new Pair { State = StyleState.Normal, StyleUrl = new Uri("flightpath-normal", UriKind.Relative) });
styleMap.Add(new Pair { State = StyleState.Highlight, StyleUrl = new Uri("flightpath-normal", UriKind.Relative) });
var styleNormal = new Style
{
Id = "flightpath-normal",
Line = new LineStyle
{
Color = new Color32(255, 10, 10, 138),
Width = 5
}
};
output.AddStyle(styleMap);
output.AddStyle(styleNormal);
}
protected void AddIconStyleMap(string id, double scale, string iconUrl, string listUrl, Hotspot iconHotspot = null)
{
var styleMap = new StyleMapCollection { Id = id };
styleMap.Add(new Pair { State = StyleState.Normal, StyleUrl = new Uri(id + "-normal", UriKind.Relative) });
styleMap.Add(new Pair { State = StyleState.Highlight, StyleUrl = new Uri(id + "-normal", UriKind.Relative) });
var styleNormal = new Style
{
Id = id + "-normal",
Icon = new IconStyle
{
Scale = scale,
Icon = new IconStyle.IconLink(new Uri(iconUrl, UriKind.Absolute)),
Hotspot = iconHotspot
},
};
if (!string.IsNullOrEmpty(listUrl))
{
styleNormal.List = new ListStyle();
styleNormal.List.AddItemIcon(new ItemIcon
{
Href = new Uri(listUrl, UriKind.Absolute)
});
}
output.AddStyle(styleMap);
output.AddStyle(styleNormal);
}
protected string GetPointDate()
{
var now = DateTime.Now;
// If the date hasn't changed since the last point label, just return the time
if (now.Date == lastPointDate)
return now.ToString("T");
lastPointDate = now.Date;
return now.ToString("F");
}
protected void AddPoint(Vector coordinate, string label, string styleMapId, bool includeTimestamp = true)
{
var point = new Point { Coordinate = coordinate, AltitudeMode = AltitudeMode.Absolute };
var placemark = new Placemark
{
Name = includeTimestamp ? $"{label} ({GetPointDate()})" : label,
StyleUrl = new Uri("#" + styleMapId, UriKind.Relative),
Geometry = point,
Visibility = true
};
rootFolder.AddFeature(placemark);
}
private const float MetersPerFoot = 0.3048f;
public async Task LogPosition(DateTime eventTime, FlightPosition position)
{
EnsureOutput(eventTime);
var altitudeMeters = position.Altitude * MetersPerFoot;
var coordinate = new Vector(position.Latitude, position.Longitude, altitudeMeters);
if (lastPosition == null)
AddPoint(coordinate, "Start", "start");
if (lastSpeed > 0 && position.Airspeed == 0)
AddPoint(coordinate, "Full stop", "fullstop");
lastPosition = coordinate;
lastSpeed = position.Airspeed;
positionPath.Coordinates.Add(coordinate);
await AutoFlush();
}
// TODO log events, engine stop etc.
}
}