using System; using System.Text; using System.Collections.Generic; using System.Collections; /// /// Author: Gabriel Svennerberg /// Created: November 2008 /// E-mail: gabriel@svennerberg.com /// Url: http://www.svennerberg.com/ /// /// Version: 0.2 /// Reimplementation of Mark Rambows Java PolylineEncoder /// which is a reimplementation of Mark McClures Javascript PolylineEncoder /// /// I have basically just ported Mark Rambows Java class to C# and made some small /// modifications that better suits me and/or C#. Fredrik Jonsson at http://www.freddes.se /// helped me with some of the conversion. /// /// All the mathematical logic is more or less copied by McClure /// For more info please visit http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/ /// /// Example usage: /// /// Create an instance of PolyLineEncoder /// /// PolylineEncoder encoder = new PolylineEncoder(); /// /// Encode the polyline /// EncodedPolyline polyLine = encoder.Encode(points); /// /// License: GPL /// May be used and changed freely /// No warranty apply /// /// public class PolylineEncoder { public int NumLevels { get; set; } public double VerySmall { get; set; } public bool ForceEndPoints { get; set; } private double[] ZoomLevelBreaks { get; set; } private int ZoomFactor { get; set; } /// /// Constructor /// /// /// /// /// public PolylineEncoder(int? numLevels, int? zoomFactor, double? verySmall, bool? forceEndpoints) { if (numLevels.HasValue) { this.NumLevels = numLevels.Value; } else { this.NumLevels = 18; } if (zoomFactor.HasValue) { this.ZoomFactor = zoomFactor.Value; } else { this.ZoomFactor = 2; } if (verySmall.HasValue) { this.VerySmall = verySmall.Value; } else { this.VerySmall = 0.00001; } if (forceEndpoints.HasValue) { this.ForceEndPoints = forceEndpoints.Value; } else { this.ForceEndPoints = true; } this.ZoomLevelBreaks = new double[NumLevels]; for (int i = 0; i < this.NumLevels; i++) { this.ZoomLevelBreaks[i] = this.VerySmall * Math.Pow(this.ZoomFactor, this.NumLevels - i - 1); } } /// /// Constructor /// public PolylineEncoder() { this.NumLevels = 18; this.ZoomFactor = 2; this.VerySmall = 0.00001; this.ForceEndPoints = true; this.ZoomLevelBreaks = new double[this.NumLevels]; for (int i = 0; i < this.NumLevels; i++) { this.ZoomLevelBreaks[i] = this.VerySmall * Math.Pow(this.ZoomFactor, this.NumLevels - i - 1); } } /// /// Douglas-Peucker algorithm, adapted for encoding /// /// /// public EncodedPolyline Encode(List points) { int i, maxLoc = 0; Stack stack = new Stack(); double[] dists = new double[points.Count]; double maxDist = 0.0, absMaxDist = 0.0, temp = 0.0; int[] current; string encodedPoints = string.Empty; string encodedLevels = string.Empty; string encodedPointsLiteral = string.Empty; if (points.Count > 2) { int[] stackVal = new int[] { 0, (points.Count - 1) }; stack.Push(stackVal); while (stack.Count > 0) { current = stack.Pop(); maxDist = 0; for (i = current[0] + 1; i < current[1]; i++) { temp = this.Distance(points[i], points[current[0]], points[current[1]]); if (temp > maxDist) { maxDist = temp; maxLoc = i; if (maxDist > absMaxDist) { absMaxDist = maxDist; } } } if (maxDist > this.VerySmall) { dists[maxLoc] = maxDist; int[] stackValCurMax = { current[0], maxLoc }; stack.Push(stackValCurMax); int[] stackValMaxCur = { maxLoc, current[1] }; stack.Push(stackValMaxCur); } } } encodedPoints = CreateEncodings(points, dists); encodedLevels = EncodeLevels(points, dists, absMaxDist); EncodedPolyline polyline = new EncodedPolyline(encodedPoints, encodedLevels, this.ZoomFactor, this.NumLevels); return polyline; } /// /// Method to make it possible to encode a polyline from a List /// of double[] with the coordinates like this /// double[0] = Latitude /// double[1] = Longitude /// /// /// An encoded polyline public EncodedPolyline EncodeFromList(List points) { List trackPoints = new List(); foreach (double[] coords in points) { Trackpoint tp = new Trackpoint(coords); trackPoints.Add(tp); } return Encode(trackPoints); } /// /// distance(p0, p1, p2) computes the distance between the point p0 and the /// segment [p1,p2]. This could probably be replaced with something that is a /// bit more numerically stable. /// /// /// /// /// double /// public double Distance(Trackpoint p0, Trackpoint p1, Trackpoint p2) { double u = 0.0; double outVal = 0.0; if (p1.Latitude == p2.Latitude && p1.Longitude== p2.Longitude) { outVal = Math.Sqrt(Math.Pow(p2.Latitude - p0.Latitude, 2) + Math.Pow(p2.Longitude - p0.Longitude, 2)); } else { u = ((p0.Latitude - p1.Latitude) * (p2.Latitude - p1.Latitude) + (p0.Longitude - p1.Longitude) * (p2.Longitude - p1.Longitude)) / (Math.Pow(p2.Latitude - p1.Latitude, 2) + Math.Pow(p2.Longitude - p1.Longitude, 2) ); if (u <= 0) { outVal = Math.Sqrt(Math.Pow(p0.Latitude - p1.Latitude, 2) + Math.Pow(p0.Longitude - p1.Longitude, 2)); } if (u >= 1) { outVal = Math.Sqrt(Math.Pow(p0.Latitude - p2.Latitude, 2) + Math.Pow(p0.Longitude - p2.Longitude, 2)); } if (0 < u && u < 1) { outVal = Math.Sqrt(Math.Pow(p0.Latitude - p1.Latitude - u * (p2.Latitude - p1.Latitude), 2) + Math.Pow(p0.Longitude - p1.Longitude - u * (p2.Longitude - p1.Longitude), 2) ); } } return outVal; } /// /// set the points that should be encoded all points have to be in /// the following form: Latitude, longitude\n /// /// /// A list of trackpoints public static List PointsToTrack(String points) { List trk = new List(); string[] str = points.Split('\n'); for (int i = 0; i < str.Length; i++) { string[] pointStrings = str[i].Split(','); trk.Add(new Trackpoint(double.Parse(pointStrings[0]), double.Parse(pointStrings[1]))); } return trk; } /// /// set the points that should be encoded all points have to be in /// the following form: Longitude,Latitude,Altitude"_"... /// /// /// public static List KmlLineStringToTrack(String points) { List trk = new List(); string[] str = points.Split(' '); for (int i = 0; i < str.Length; i++) { String[] pointStrings = str[i].Split(','); trk.Add(new Trackpoint(double.Parse(pointStrings[0]), double.Parse(pointStrings[1]))); } return trk; } private static int Floor1e5(double coordinate) { return (int)Math.Floor(coordinate * 1e5); } private static String EncodeSignedNumber(int num) { int sgn_num = num << 1; if (num < 0) { sgn_num = ~(sgn_num); } return (EncodeNumber(sgn_num)); } private static String EncodeNumber(int num) { StringBuilder encodeString = new StringBuilder(); while (num >= 0x20) { int nextValue = (0x20 | (num & 0x1f)) + 63; encodeString.Append((char)(nextValue)); num >>= 5; } num += 63; encodeString.Append((char)num); return encodeString.ToString(); } /// /// Now we can use the previous function to march down the list of points and /// encode the levels. Like createEncodings, we ignore points whose distance /// (in dists) is undefined. /// /// /// /// /// private String EncodeLevels(List points, double[] dists, double absMaxDist) { int i; StringBuilder encoded_levels = new StringBuilder(); if (this.ForceEndPoints) { encoded_levels.Append(EncodeNumber(this.NumLevels - 1)); } else { encoded_levels.Append(EncodeNumber(this.NumLevels - ComputeLevel(absMaxDist) - 1)); } for (i = 1; i < points.Count - 1; i++) { if (dists[i] != 0) { encoded_levels.Append(EncodeNumber(this.NumLevels - ComputeLevel(dists[i]) - 1)); } } if (this.ForceEndPoints) { encoded_levels.Append(EncodeNumber(this.NumLevels - 1)); } else { encoded_levels.Append(EncodeNumber(this.NumLevels - ComputeLevel(absMaxDist) - 1)); } return encoded_levels.ToString(); } /// /// This computes the appropriate zoom level of a point in terms of it's /// distance from the relevant segment in the DP algorithm. Could be done in /// terms of a logarithm, but this approach makes it a bit easier to ensure /// that the level is not too large. /// /// /// private int ComputeLevel(double absMaxDist) { int lev = 0; if (absMaxDist > this.VerySmall) { lev = 0; while (absMaxDist < (double)this.ZoomLevelBreaks[lev]) { lev++; } return lev; } return lev; } /// /// Returns the encoding of the points /// /// /// /// /// private String CreateEncodings(List points, double[] dists) { StringBuilder encodedPoints = new StringBuilder(); double maxlat = 0, minlat = 0, maxlon = 0, minlon = 0; int plat = 0, plng = 0; for (int i = 0; i < points.Count; i++) { // determin bounds (max/min lat/lon) if (i == 0) { maxlat = minlat = points[i].Latitude; maxlon = minlon = points[i].Longitude; } else { if (points[i].Latitude > maxlat) { maxlat = points[i].Latitude; } if (points[i].Latitude < minlat) { minlat = points[i].Latitude; } if (points[i].Longitude > maxlon) { maxlon = points[i].Longitude; } if (points[i].Longitude < minlon) { minlon = points[i].Longitude; } } if (dists[i] != 0 || i == 0 || i == points.Count - 1) { Trackpoint point = points[i]; int late5 = Floor1e5(point.Latitude); int lnge5 = Floor1e5(point.Longitude); int dlat = late5 - plat; int dlng = lnge5 - plng; plat = late5; plng = lnge5; encodedPoints.Append(EncodeSignedNumber(dlat)); encodedPoints.Append(EncodeSignedNumber(dlng)); } } return encodedPoints.ToString(); } public static Hashtable CreateEncodings(List track, int level, int step) { Hashtable resultMap = new Hashtable(); StringBuilder encodedPoints = new StringBuilder(); StringBuilder encodedLevels = new StringBuilder(); int plat = 0; int plng = 0; int counter = 0; int listSize = track.Count; Trackpoint trackpoint; for (int i = 0; i < listSize; i += step) { counter++; trackpoint = (Trackpoint)track[i]; int late5 = Floor1e5(trackpoint.Latitude); int lnge5 = Floor1e5(trackpoint.Longitude); int dlat = late5 - plat; int dlng = lnge5 - plng; plat = late5; plng = lnge5; encodedPoints.Append(EncodeSignedNumber(dlat)).Append(EncodeSignedNumber(dlng)); encodedLevels.Append(EncodeNumber(level)); } resultMap.Add("encodedPoints", encodedPoints.ToString()); resultMap.Add("encodedLevels", encodedLevels.ToString()); return resultMap; } } /// /// A class representing a point (coordinates) /// public class Trackpoint { public double Latitude { get; set; } public double Longitude { get; set; } public double Altitude { get; set; } public Trackpoint(double lat, double lon) { this.Latitude = lat; this.Longitude = lon; } public Trackpoint(double lat, double lon, double altitude) { this.Latitude = lat; this.Longitude = lon; this.Altitude = altitude; } public Trackpoint(double[] coords) { this.Latitude = coords[0]; this.Longitude = coords[1]; } } /// /// A class representing an encoded polyline /// public class EncodedPolyline { public string Points { get; set; } public string Levels { get; set; } public int ZoomFactor { get; set; } public int NumLevels { get; set; } public EncodedPolyline(string points, string levels, int zoomFactor, int numLevels) { this.Points = points; this.Levels = levels; this.ZoomFactor = zoomFactor; this.NumLevels = numLevels; } }