using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using GeoAPI.Geometries;
using GisSharpBlog.NetTopologySuite.Geometries;
using GisSharpBlog.NetTopologySuite.Utilities;
using RTools_NTS.Util;
namespace GisSharpBlog.NetTopologySuite.IO
{
///
/// Converts a Well-Known Text string to a Geometry.
///
/// The WKTReader allows
/// extracting Geometry objects from either input streams or
/// internal strings. This allows it to function as a parser to read Geometry
/// objects from text blocks embedded in other data formats (e.g. XML).
///
/// The Well-known
/// Text format is defined in the
/// OpenGIS Simple Features Specification for SQL .
///
/// NOTE: There is an inconsistency in the SFS.
/// The WKT grammar states that MultiPoints are represented by
/// MULTIPOINT ( ( x y), (x y) ),
/// but the examples show MultiPoints as MULTIPOINT ( x y, x y ).
/// Other implementations follow the latter syntax, so NTS will adopt it as well.
/// A WKTReader is parameterized by a GeometryFactory,
/// to allow it to create Geometry objects of the appropriate
/// implementation. In particular, the GeometryFactory will
/// determine the PrecisionModel and SRID that is used.
/// The WKTReader will convert the input numbers to the precise
/// internal representation.
///
/// reads also non-standard "LINEARRING" tags.
///
///
public class WKTReader
{
private readonly IGeometryFactory geometryFactory;
private readonly IPrecisionModel precisionModel;
private int index;
///
/// Creates a WKTReader that creates objects using a basic GeometryFactory.
///
public WKTReader() : this(GeometryFactory.Default) {}
///
/// Creates a WKTReader that creates objects using the given
/// GeometryFactory.
///
/// The factory used to create Geometrys.
public WKTReader(IGeometryFactory geometryFactory)
{
this.geometryFactory = geometryFactory;
precisionModel = geometryFactory.PrecisionModel;
}
///
/// Converts a Well-known Text representation to a Geometry.
///
///
/// one or more Geometry Tagged Text strings (see the OpenGIS
/// Simple Features Specification) separated by whitespace.
///
///
/// A Geometry specified by wellKnownText
///
public IGeometry Read(string wellKnownText)
{
using (StringReader reader = new StringReader(wellKnownText))
{
return Read(reader);
}
}
///
/// Converts a Well-known Text representation to a Geometry.
///
///
/// A Reader which will return a "Geometry Tagged Text"
/// string (see the OpenGIS Simple Features Specification).
///
/// A Geometry read from reader.
///
public IGeometry Read(TextReader reader)
{
StreamTokenizer tokenizer = new StreamTokenizer(reader);
ArrayList tokens = new ArrayList();
tokenizer.Tokenize(tokens); // Read directly all tokens
index = 0; // Reset pointer to start of tokens
try
{
return ReadGeometryTaggedText(tokens);
}
catch (IOException e)
{
throw new ParseException(e.ToString());
}
}
///
/// Returns the next array of Coordinates in the stream.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next element returned by the stream should be "(" (the
/// beginning of "(x1 y1, x2 y2, ..., xn yn)") or "EMPTY".
///
///
/// if set to true skip extra parenthesis around coordinates.
///
///
/// The next array of Coordinates in the
/// stream, or an empty array if "EMPTY" is the next element returned by
/// the stream.
///
private ICoordinate[] GetCoordinates(IList tokens, Boolean skipExtraParenthesis)
{
string nextToken = GetNextEmptyOrOpener(tokens);
if (nextToken.Equals("EMPTY"))
{
return new ICoordinate[]
{};
}
List coordinates = new List();
coordinates.Add(GetPreciseCoordinate(tokens, skipExtraParenthesis));
nextToken = GetNextCloserOrComma(tokens);
while (nextToken.Equals(","))
{
coordinates.Add(GetPreciseCoordinate(tokens, skipExtraParenthesis));
nextToken = GetNextCloserOrComma(tokens);
}
return coordinates.ToArray();
}
///
///
///
///
///
private ICoordinate GetPreciseCoordinate(IList tokens, Boolean skipExtraParenthesis)
{
ICoordinate coord = new Coordinate();
Boolean extraParenthesisFound = false;
if (skipExtraParenthesis)
{
extraParenthesisFound = IsStringValueNext(tokens, "(");
if (extraParenthesisFound)
{
index++;
}
}
coord.X = GetNextNumber(tokens);
coord.Y = GetNextNumber(tokens);
if (IsNumberNext(tokens))
{
coord.Z = GetNextNumber(tokens);
}
if (skipExtraParenthesis &&
extraParenthesisFound &&
IsStringValueNext(tokens, ")"))
{
index++;
}
precisionModel.MakePrecise(coord);
return coord;
}
private Boolean IsStringValueNext(IList tokens, String stringValue)
{
Token token = tokens[index] as Token;
return token.StringValue == stringValue;
}
///
///
///
///
///
private bool IsNumberNext(IList tokens)
{
Token token = tokens[index] as Token;
return token is FloatToken || token is IntToken;
}
///
/// Returns the next number in the stream.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next token must be a number.
///
/// The next number in the stream.
private double GetNextNumber(IList tokens)
{
Token token = tokens[index++] as Token;
if (token == null)
{
throw new ArgumentNullException("tokens", "Token list contains a null value");
}
else if (token is EofToken)
{
throw new ParseException("Expected number but encountered end of stream");
}
else if (token is EolToken)
{
throw new ParseException("Expected number but encountered end of line");
}
else if (token is FloatToken || token is IntToken)
{
return (double) token.ConvertToType(typeof(double));
}
else if (token is WordToken)
{
throw new ParseException("Expected number but encountered word: " + token.StringValue);
}
else if (token.StringValue == "(")
{
throw new ParseException("Expected number but encountered '('");
}
else if (token.StringValue == ")")
{
throw new ParseException("Expected number but encountered ')'");
}
else if (token.StringValue == ",")
{
throw new ParseException("Expected number but encountered ','");
}
else
{
Assert.ShouldNeverReachHere();
return double.NaN;
}
}
///
/// Returns the next "EMPTY" or "(" in the stream as uppercase text.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next token must be "EMPTY" or "(".
///
///
/// The next "EMPTY" or "(" in the stream as uppercase text.
private string GetNextEmptyOrOpener(IList tokens)
{
string nextWord = GetNextWord(tokens);
if (nextWord.Equals("EMPTY") || nextWord.Equals("("))
{
return nextWord;
}
throw new ParseException("Expected 'EMPTY' or '(' but encountered '" + nextWord + "'");
}
///
/// Returns the next ")" or "," in the stream.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next token must be ")" or ",".
///
///
/// The next ")" or "," in the stream.
private string GetNextCloserOrComma(IList tokens)
{
string nextWord = GetNextWord(tokens);
if (nextWord.Equals(",") || nextWord.Equals(")"))
{
return nextWord;
}
throw new ParseException("Expected ')' or ',' but encountered '" + nextWord
+ "'");
}
///
/// Returns the next ")" in the stream.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next token must be ")".
///
///
/// The next ")" in the stream.
private string GetNextCloser(IList tokens)
{
string nextWord = GetNextWord(tokens);
if (nextWord.Equals(")"))
{
return nextWord;
}
throw new ParseException("Expected ')' but encountered '" + nextWord + "'");
}
///
/// Returns the next word in the stream as uppercase text.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next token must be a word.
///
/// The next word in the stream as uppercase text.
private string GetNextWord(IList tokens)
{
Token token = tokens[index++] as Token;
if (token is EofToken)
{
throw new ParseException("Expected number but encountered end of stream");
}
else if (token is EolToken)
{
throw new ParseException("Expected number but encountered end of line");
}
else if (token is FloatToken || token is IntToken)
{
throw new ParseException("Expected word but encountered number: " + token.StringValue);
}
else if (token is WordToken)
{
return token.StringValue.ToUpper();
}
else if (token.StringValue == "(")
{
return "(";
}
else if (token.StringValue == ")")
{
return ")";
}
else if (token.StringValue == ",")
{
return ",";
}
else
{
Assert.ShouldNeverReachHere();
return null;
}
}
///
/// Creates a Geometry using the next token in the stream.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next tokens must form a <Geometry Tagged Text.
///
/// A Geometry specified by the next token
/// in the stream.
private IGeometry ReadGeometryTaggedText(IList tokens)
{
/*
* A new different implementation by Marc Jacquin:
* this code manages also SRID values.
*/
IGeometry returned;
string sridValue = null;
string type = tokens[0].ToString();
if (type == "SRID")
{
sridValue = tokens[2].ToString();
// tokens.RemoveRange(0, 4);
tokens.RemoveAt(0);
tokens.RemoveAt(0);
tokens.RemoveAt(0);
tokens.RemoveAt(0);
}
else
{
type = GetNextWord(tokens);
}
if (type.Equals("POINT"))
{
returned = ReadPointText(tokens);
}
else if (type.Equals("LINESTRING"))
{
returned = ReadLineStringText(tokens);
}
else if (type.Equals("LINEARRING"))
{
returned = ReadLinearRingText(tokens);
}
else if (type.Equals("POLYGON"))
{
returned = ReadPolygonText(tokens);
}
else if (type.Equals("MULTIPOINT"))
{
returned = ReadMultiPointText(tokens);
}
else if (type.Equals("MULTILINESTRING"))
{
returned = ReadMultiLineStringText(tokens);
}
else if (type.Equals("MULTIPOLYGON"))
{
returned = ReadMultiPolygonText(tokens);
}
else if (type.Equals("GEOMETRYCOLLECTION"))
{
returned = ReadGeometryCollectionText(tokens);
}
else
{
throw new ParseException("Unknown type: " + type);
}
if (returned == null)
{
throw new NullReferenceException("Error reading geometry");
}
if (sridValue != null)
{
returned.SRID = Convert.ToInt32(sridValue);
}
return returned;
}
///
/// Creates a Point using the next token in the stream.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next tokens must form a <Point Text.
///
/// A Point specified by the next token in
/// the stream.
private IPoint ReadPointText(IList tokens)
{
string nextToken = GetNextEmptyOrOpener(tokens);
if (nextToken.Equals("EMPTY"))
{
return geometryFactory.CreatePoint((ICoordinate) null);
}
IPoint point = geometryFactory.CreatePoint(GetPreciseCoordinate(tokens, false));
GetNextCloser(tokens);
return point;
}
///
/// Creates a LineString using the next token in the stream.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next tokens must form a <LineString Text.
///
///
/// A LineString specified by the next
/// token in the stream.
private ILineString ReadLineStringText(IList tokens)
{
return geometryFactory.CreateLineString(GetCoordinates(tokens, false));
}
///
/// Creates a LinearRing using the next token in the stream.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next tokens must form a <LineString Text.
///
/// A LinearRing specified by the next
/// token in the stream.
private ILinearRing ReadLinearRingText(IList tokens)
{
return geometryFactory.CreateLinearRing(GetCoordinates(tokens, false));
}
///
/// Creates a MultiPoint using the next token in the stream.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next tokens must form a <MultiPoint Text.
///
///
/// A MultiPoint specified by the next
/// token in the stream.
private IMultiPoint ReadMultiPointText(IList tokens)
{
return geometryFactory.CreateMultiPoint(ToPoints(GetCoordinates(tokens, true)));
}
///
/// Creates an array of Points having the given Coordinates.
///
///
/// The Coordinates with which to create the Points
///
///
/// Points created using this WKTReader
/// s GeometryFactory.
///
private IPoint[] ToPoints(ICoordinate[] coordinates)
{
List points = new List();
for (int i = 0; i < coordinates.Length; i++)
{
points.Add(geometryFactory.CreatePoint(coordinates[i]));
}
return points.ToArray();
}
///
/// Creates a Polygon using the next token in the stream.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next tokens must form a Polygon Text.
///
///
/// A Polygon specified by the next token
/// in the stream.
///
private IPolygon ReadPolygonText(IList tokens)
{
string nextToken = GetNextEmptyOrOpener(tokens);
if (nextToken.Equals("EMPTY"))
{
return geometryFactory.CreatePolygon(
geometryFactory.CreateLinearRing(new ICoordinate[]
{}), new ILinearRing[]
{});
}
List holes = new List();
ILinearRing shell = ReadLinearRingText(tokens);
nextToken = GetNextCloserOrComma(tokens);
while (nextToken.Equals(","))
{
ILinearRing hole = ReadLinearRingText(tokens);
holes.Add(hole);
nextToken = GetNextCloserOrComma(tokens);
}
return geometryFactory.CreatePolygon(shell, holes.ToArray());
}
///
/// Creates a MultiLineString using the next token in the stream.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next tokens must form a MultiLineString Text.
///
///
/// A MultiLineString specified by the
/// next token in the stream.
private IMultiLineString ReadMultiLineStringText(IList tokens)
{
string nextToken = GetNextEmptyOrOpener(tokens);
if (nextToken.Equals("EMPTY"))
{
return geometryFactory.CreateMultiLineString(new ILineString[]
{});
}
List lineStrings = new List();
ILineString lineString = ReadLineStringText(tokens);
lineStrings.Add(lineString);
nextToken = GetNextCloserOrComma(tokens);
while (nextToken.Equals(","))
{
lineString = ReadLineStringText(tokens);
lineStrings.Add(lineString);
nextToken = GetNextCloserOrComma(tokens);
}
return geometryFactory.CreateMultiLineString(lineStrings.ToArray());
}
///
/// Creates a MultiPolygon using the next token in the stream.
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next tokens must form a MultiPolygon Text.
///
///
/// A MultiPolygon specified by the next
/// token in the stream, or if if the coordinates used to create the
/// Polygon shells and holes do not form closed linestrings.
private IMultiPolygon ReadMultiPolygonText(IList tokens)
{
string nextToken = GetNextEmptyOrOpener(tokens);
if (nextToken.Equals("EMPTY"))
{
return geometryFactory.CreateMultiPolygon(new IPolygon[]
{});
}
List polygons = new List();
IPolygon polygon = ReadPolygonText(tokens);
polygons.Add(polygon);
nextToken = GetNextCloserOrComma(tokens);
while (nextToken.Equals(","))
{
polygon = ReadPolygonText(tokens);
polygons.Add(polygon);
nextToken = GetNextCloserOrComma(tokens);
}
return geometryFactory.CreateMultiPolygon(polygons.ToArray());
}
///
/// Creates a GeometryCollection using the next token in the
/// stream.
///
///
/// Tokenizer over a stream of text in Well-known Text
/// format. The next tokens must form a <GeometryCollection Text.
///
///
/// A GeometryCollection specified by the
/// next token in the stream.
private IGeometryCollection ReadGeometryCollectionText(IList tokens)
{
string nextToken = GetNextEmptyOrOpener(tokens);
if (nextToken.Equals("EMPTY"))
{
return geometryFactory.CreateGeometryCollection(new IGeometry[]
{});
}
List geometries = new List();
IGeometry geometry = ReadGeometryTaggedText(tokens);
geometries.Add(geometry);
nextToken = GetNextCloserOrComma(tokens);
while (nextToken.Equals(","))
{
geometry = ReadGeometryTaggedText(tokens);
geometries.Add(geometry);
nextToken = GetNextCloserOrComma(tokens);
}
return geometryFactory.CreateGeometryCollection(geometries.ToArray());
}
}
}