using System; using System.Globalization; using System.IO; using System.Text; using GeoAPI.Geometries; using GisSharpBlog.NetTopologySuite.Geometries; using GisSharpBlog.NetTopologySuite.Utilities; namespace GisSharpBlog.NetTopologySuite.IO { /// /// Outputs the textual representation of a . /// The outputs coordinates rounded to the precision /// model. No more than the maximum number of necessary decimal places will be /// output. /// The Well-known Text format is defined in the OpenGIS Simple Features /// Specification for SQL. /// A non-standard "LINEARRING" tag is used for LinearRings. The WKT spec does /// not define a special tag for LinearRings. The standard tag to use is /// "LINESTRING". /// public class WKTWriter { // NOTE: modified for "safe" assembly in Sql 2005 // const added! private const int WKTWriterIndent = 2; private readonly string MaxPrecisionFormat = "{0:R}"; private NumberFormatInfo formatter; private string format; private bool isFormatted; private bool useMaxPrecision; /// /// Generates the WKT for a Point. /// /// The point coordinate. /// public static String ToPoint(ICoordinate p0) { if (double.IsNaN(p0.Z)) { return "POINT(" + p0.X + " " + p0.Y + ")"; } else { return "POINT(" + p0.X + " " + p0.Y + " " + p0.Z + ")"; } } /// /// Generates the WKT for a N-point LineString. /// /// The sequence to output. /// public static String ToLineString(ICoordinateSequence seq) { var buf = new StringBuilder(); buf.Append("LINESTRING"); if (seq.Count == 0) { buf.Append(" EMPTY"); } else { buf.Append("("); for (var i = 0; i < seq.Count; i++) { if (i > 0) { buf.Append(","); } buf.Append(seq.GetX(i) + " " + seq.GetY(i)); } buf.Append(")"); } return buf.ToString(); } /// /// Generates the WKT for a 2-point LineString. /// /// The first coordinate. /// The second coordinate. /// public static String ToLineString(ICoordinate p0, ICoordinate p1) { if (double.IsNaN(p0.Z)) { return "LINESTRING(" + p0.X + " " + p0.Y + "," + p1.X + " " + p1.Y + ")"; } else { return "LINESTRING(" + p0.X + " " + p0.Y + " " + p0.Z + "," + p1.X + " " + p1.Y + " " + p1.Z + ")"; } } /// /// Returns a String of repeated characters. /// /// The character to repeat. /// The number of times to repeat the character. /// A string of characters. public static string StringOfChar(char ch, int count) { var buf = new StringBuilder(); for (var i = 0; i < count; i++) { buf.Append(ch); } return buf.ToString(); } /// /// Converts a Geometry to its Well-known Text representation. /// /// A Geometry to process. /// A Geometry Tagged Text string (see the OpenGIS Simple Features Specification). public virtual string Write(IGeometry geometry) { TextWriter sw = new StringWriter(); try { WriteFormatted(geometry, false, sw); } catch (IOException) { Assert.ShouldNeverReachHere(); } return sw.ToString(); } /// /// Converts a Geometry to its Well-known Text representation. /// /// A Geometry to process. /// /// A "Geometry Tagged Text" string (see the OpenGIS Simple Features Specification) public virtual void Write(IGeometry geometry, TextWriter writer) { WriteFormatted(geometry, false, writer); } /// /// Same as write, but with newlines and spaces to make the /// well-known text more readable. /// /// A Geometry to process /// /// A "Geometry Tagged Text" string (see the OpenGIS Simple /// Features Specification), with newlines and spaces. /// public virtual string WriteFormatted(IGeometry geometry) { TextWriter sw = new StringWriter(); try { WriteFormatted(geometry, true, sw); } catch (IOException) { Assert.ShouldNeverReachHere(); } return sw.ToString(); } /// /// Same as write, but with newlines and spaces to make the /// well-known text more readable. /// /// A Geometry to process /// /// /// A Geometry Tagged Text string (see the OpenGIS Simple /// Features Specification), with newlines and spaces. /// public virtual void WriteFormatted(IGeometry geometry, TextWriter writer) { WriteFormatted(geometry, true, writer); } /// /// Creates the NumberFormatInfo used to write doubles /// with a sufficient number of decimal places. /// /// /// The PrecisionModel used to determine /// the number of decimal places to write. /// /// /// A NumberFormatInfo that write double /// s without scientific notation. /// private static NumberFormatInfo CreateFormatter(IPrecisionModel precisionModel) { // the default number of decimal places is 16, which is sufficient // to accomodate the maximum precision of a double. var decimalPlaces = precisionModel.MaximumSignificantDigits; // specify decimal separator explicitly to avoid problems in other locales var nfi = new NumberFormatInfo { NumberDecimalSeparator = ".", NumberDecimalDigits = decimalPlaces, NumberGroupSeparator = String.Empty, NumberGroupSizes = new int[] {} }; return nfi; } /// /// Converts a Geometry to its Well-known Text representation. /// /// A Geometry to process /// /// /// /// A "Geometry Tagged Text" string (see the OpenGIS Simple /// Features Specification). /// private void WriteFormatted(IGeometry geometry, bool formatted, TextWriter writer) { if (geometry == null) { throw new ArgumentNullException("geometry"); } // Enable maxPrecision (via {0:R} formatter) in WriteNumber method useMaxPrecision = geometry.Factory.PrecisionModel.PrecisionModelType == PrecisionModels.Floating; isFormatted = formatted; formatter = CreateFormatter(geometry.PrecisionModel); format = "0." + StringOfChar('#', formatter.NumberDecimalDigits); AppendGeometryTaggedText(geometry, 0, writer); // Disable maxPrecision as default setting useMaxPrecision = false; } /// /// Converts a Geometry to <Geometry Tagged Text format, /// then appends it to the writer. /// /// /he Geometry to process. /// /// /he output writer to append to. private void AppendGeometryTaggedText(IGeometry geometry, int level, TextWriter writer) { Indent(level, writer); if (geometry is IPoint) { var point = (IPoint) geometry; AppendPointTaggedText(point.Coordinate, level, writer, point.PrecisionModel); } else if (geometry is ILinearRing) { AppendLinearRingTaggedText((ILinearRing) geometry, level, writer); } else if (geometry is ILineString) { AppendLineStringTaggedText((ILineString) geometry, level, writer); } else if (geometry is IPolygon) { AppendPolygonTaggedText((IPolygon) geometry, level, writer); } else if (geometry is IMultiPoint) { AppendMultiPointTaggedText((IMultiPoint) geometry, level, writer); } else if (geometry is IMultiLineString) { AppendMultiLineStringTaggedText((IMultiLineString) geometry, level, writer); } else if (geometry is IMultiPolygon) { AppendMultiPolygonTaggedText((IMultiPolygon) geometry, level, writer); } else if (geometry is IGeometryCollection) { AppendGeometryCollectionTaggedText((IGeometryCollection) geometry, level, writer); } else { Assert.ShouldNeverReachHere("Unsupported Geometry implementation:" + geometry.GetType()); } } /// /// Converts a Coordinate to Point Tagged Text format, /// then appends it to the writer. /// /// The Coordinate to process. /// /// The output writer to append to. /// /// The PrecisionModel to use to convert /// from a precise coordinate to an external coordinate. /// private void AppendPointTaggedText(ICoordinate coordinate, int level, TextWriter writer, IPrecisionModel precisionModel) { writer.Write("POINT"); AppendPointText(coordinate, level, writer, precisionModel); } /// /// Converts a LineString to <LineString Tagged Text /// format, then appends it to the writer. /// /// The LineString to process. /// /// The output writer to append to. private void AppendLineStringTaggedText(ILineString lineString, int level, TextWriter writer) { writer.Write("LINESTRING"); AppendLineStringText(lineString, level, false, writer); } /// /// Converts a LinearRing to <LinearRing Tagged Text /// format, then appends it to the writer. /// /// The LinearRing to process. /// /// The output writer to append to. private void AppendLinearRingTaggedText(ILinearRing linearRing, int level, TextWriter writer) { writer.Write("LINEARRING"); AppendLineStringText(linearRing, level, false, writer); } /// /// Converts a Polygon to Polygon Tagged Text format, /// then appends it to the writer. /// /// The Polygon to process. /// /// The output writer to append to. private void AppendPolygonTaggedText(IPolygon polygon, int level, TextWriter writer) { writer.Write("POLYGON"); AppendPolygonText(polygon, level, false, writer); } /// /// Converts a MultiPoint to <MultiPoint Tagged Text /// format, then appends it to the writer. /// /// The MultiPoint to process. /// /// The output writer to append to. private void AppendMultiPointTaggedText(IMultiPoint multipoint, int level, TextWriter writer) { writer.Write("MULTIPOINT"); AppendMultiPointText(multipoint, level, writer); } /// /// Converts a MultiLineString to MultiLineString Tagged /// Text format, then appends it to the writer. /// /// The MultiLineString to process. /// /// The output writer to append to. private void AppendMultiLineStringTaggedText(IMultiLineString multiLineString, int level, TextWriter writer) { writer.Write("MULTILINESTRING"); AppendMultiLineStringText(multiLineString, level, false, writer); } /// /// Converts a MultiPolygon to MultiPolygon Tagged Text /// format, then appends it to the writer. /// /// The MultiPolygon to process. /// /// The output writer to append to. private void AppendMultiPolygonTaggedText(IMultiPolygon multiPolygon, int level, TextWriter writer) { writer.Write("MULTIPOLYGON"); AppendMultiPolygonText(multiPolygon, level, writer); } /// /// Converts a GeometryCollection to GeometryCollection /// Tagged Text format, then appends it to the writer. /// /// The GeometryCollection to process. /// /// The output writer to append to. private void AppendGeometryCollectionTaggedText(IGeometryCollection geometryCollection, int level, TextWriter writer) { writer.Write("GEOMETRYCOLLECTION"); AppendGeometryCollectionText(geometryCollection, level, writer); } /// /// Converts a Coordinate to Point Text format, then /// appends it to the writer. /// /// The Coordinate to process. /// /// The output writer to append to. /// /// The PrecisionModel to use to convert /// from a precise coordinate to an external coordinate. /// private void AppendPointText(ICoordinate coordinate, int level, TextWriter writer, IPrecisionModel precisionModel) { if (coordinate == null) { writer.Write(" EMPTY"); } else { writer.Write("("); AppendCoordinate(coordinate, writer, precisionModel); writer.Write(")"); } } /// /// Converts a Coordinate to Point format, then appends /// it to the writer. /// /// The Coordinate to process. /// The output writer to append to. /// /// The PrecisionModel to use to convert /// from a precise coordinate to an external coordinate. /// private void AppendCoordinate(ICoordinate coordinate, TextWriter writer, IPrecisionModel precisionModel) { writer.Write(WriteNumber(coordinate.X) + " " + WriteNumber(coordinate.Y)); if (!double.IsNaN(coordinate.Z)) { writer.Write(" " + WriteNumber(coordinate.Z)); } } /// /// Converts a to a , /// not in scientific notation. /// /// The to convert. /// /// The as a , /// not in scientific notation. /// private string WriteNumber(double d) { var standard = d.ToString(format, formatter); if (!useMaxPrecision) { return standard; } try { var converted = Convert.ToDouble(standard, formatter); // Check if some precision is lost during text conversion: if so, use {0:R} formatter if (converted != d) { return String.Format(formatter, MaxPrecisionFormat, d); } return standard; } catch (OverflowException ex) { // http://groups.google.com/group/microsoft.public.dotnet.framework/browse_thread/thread/ed77db2f3fcb5a4a // Use MaxPrecisionFormat return String.Format(formatter, MaxPrecisionFormat, d); } } /// /// Converts a LineString to <LineString Text format, then /// appends it to the writer. /// /// The LineString to process. /// /// /// The output writer to append to. private void AppendLineStringText(ILineString lineString, int level, bool doIndent, TextWriter writer) { if (lineString.IsEmpty) { writer.Write(" EMPTY"); } else { if (doIndent) { Indent(level, writer); } writer.Write("("); for (var i = 0; i < lineString.NumPoints; i++) { if (i > 0) { writer.Write(","); if (i%10 == 0) { Indent(level + 2, writer); } } AppendCoordinate(lineString.GetCoordinateN(i), writer, lineString.PrecisionModel); } writer.Write(")"); } } /// /// Converts a Polygon to Polygon Text format, then /// appends it to the writer. /// /// The Polygon to process. /// /// /// The output writer to append to. private void AppendPolygonText(IPolygon polygon, int level, bool indentFirst, TextWriter writer) { if (polygon.IsEmpty) { writer.Write(" EMPTY"); } else { if (indentFirst) { Indent(level, writer); } writer.Write("("); AppendLineStringText(polygon.ExteriorRing, level, false, writer); for (var i = 0; i < polygon.NumInteriorRings; i++) { writer.Write(","); AppendLineStringText(polygon.GetInteriorRingN(i), level + 1, true, writer); } writer.Write(")"); } } /// /// Converts a MultiPoint to <MultiPoint Text format, then /// appends it to the writer. /// /// The MultiPoint to process. /// /// The output writer to append to. private void AppendMultiPointText(IMultiPoint multiPoint, int level, TextWriter writer) { if (multiPoint.IsEmpty) { writer.Write(" EMPTY"); } else { writer.Write("("); for (var i = 0; i < multiPoint.NumGeometries; i++) { if (i > 0) { writer.Write(","); } AppendCoordinate((multiPoint.GetGeometryN(i)).Coordinate, writer, multiPoint.PrecisionModel); } writer.Write(")"); } } /// /// Converts a MultiLineString to <MultiLineString Text /// format, then appends it to the writer. /// /// The MultiLineString to process. /// /// /// The output writer to append to. private void AppendMultiLineStringText(IMultiLineString multiLineString, int level, bool indentFirst, TextWriter writer) { if (multiLineString.IsEmpty) { writer.Write(" EMPTY"); } else { var level2 = level; var doIndent = indentFirst; writer.Write("("); for (var i = 0; i < multiLineString.NumGeometries; i++) { if (i > 0) { writer.Write(","); level2 = level + 1; doIndent = true; } AppendLineStringText((ILineString) multiLineString.GetGeometryN(i), level2, doIndent, writer); } writer.Write(")"); } } /// /// Converts a MultiPolygon to <MultiPolygon Text format, /// then appends it to the writer. /// /// The MultiPolygon to process. /// /// The output writer to append to. private void AppendMultiPolygonText(IMultiPolygon multiPolygon, int level, TextWriter writer) { if (multiPolygon.IsEmpty) { writer.Write(" EMPTY"); } else { var level2 = level; var doIndent = false; writer.Write("("); for (var i = 0; i < multiPolygon.NumGeometries; i++) { if (i > 0) { writer.Write(","); level2 = level + 1; doIndent = true; } AppendPolygonText((IPolygon) multiPolygon.GetGeometryN(i), level2, doIndent, writer); } writer.Write(")"); } } /// /// Converts a GeometryCollection to GeometryCollectionText /// format, then appends it to the writer. /// /// The GeometryCollection to process. /// /// The output writer to append to. private void AppendGeometryCollectionText(IGeometryCollection geometryCollection, int level, TextWriter writer) { if (geometryCollection.IsEmpty) { writer.Write(" EMPTY"); } else { var level2 = level; writer.Write("("); for (var i = 0; i < geometryCollection.NumGeometries; i++) { if (i > 0) { writer.Write(","); level2 = level + 1; } AppendGeometryTaggedText(geometryCollection.GetGeometryN(i), level2, writer); } writer.Write(")"); } } /// /// /// /// /// private void Indent(int level, TextWriter writer) { if (!isFormatted || level <= 0) { return; } writer.Write("\n"); writer.Write(StringOfChar(' ', WKTWriterIndent*level)); } } }