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