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));
}
}
}