#region Disclaimer / License // Copyright (C) 2012, Jackie Ng // http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie@gmail.com // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion using System; using System.Collections; using System.Collections.Generic; using System.IO; using GeoAPI.Geometries; using GisSharpBlog.NetTopologySuite.Geometries; using RTools_NTS.Util; using GisSharpBlog.NetTopologySuite.IO; using GisSharpBlog.NetTopologySuite.Utilities; namespace OSGeo.MapGuide.MaestroAPI.Internal { /// /// A fixed version of WKTReader that can parse 3D geometry WKT /// public class FixedWKTReader { private IGeometryFactory geometryFactory; private IPrecisionModel precisionModel; int index; /// /// Creates a WKTReader that creates objects using a basic GeometryFactory. /// public FixedWKTReader() : this(GeometryFactory.Default) { } /// /// Creates a WKTReader that creates objects using the given /// GeometryFactory. /// /// The factory used to create Geometrys. public FixedWKTReader(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); var tokens = new List(); 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")) //NOXLATE return new ICoordinate[] { }; List coordinates = new List(); coordinates.Add(GetPreciseCoordinate(tokens, skipExtraParenthesis)); nextToken = GetNextCloserOrComma(tokens); while (nextToken.Equals(",")) //NOXLATE { 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, "("); //NOXLATE if (extraParenthesisFound) { index++; } } coord.X = GetNextNumber(tokens); coord.Y = GetNextNumber(tokens); if (IsNumberNext(tokens)) coord.Z = GetNextNumber(tokens); if (IsNumberNext(tokens)) coord.M = GetNextNumber(tokens); if (skipExtraParenthesis && extraParenthesisFound && IsStringValueNext(tokens, ")")) //NOXLATE { 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", Strings.ErrorTokenListContainsNullValue); //NOXLATE else if (token is EofToken) throw new ParseException(Strings.ErrorParseExpectedNumberEos); else if (token is EolToken) throw new ParseException(Strings.ErrorParseExpectedNumberEol); else if (token is FloatToken || token is IntToken) return (double)token.ConvertToType(typeof(double)); else if (token is WordToken) throw new ParseException(string.Format(Strings.ErrorParseExpectedNumberGotWord, token.StringValue)); else if (token.StringValue == "(") //NOXLATE throw new ParseException(string.Format(Strings.ErrorParseExpectedNumber, '(')); //NOXLATE else if (token.StringValue == ")") //NOXLATE throw new ParseException(string.Format(Strings.ErrorParseExpectedNumber, ')')); //NOXLATE else if (token.StringValue == ",") //NOXLATE throw new ParseException(string.Format(Strings.ErrorParseExpectedNumber, ',')); //NOXLATE 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) { //The next word may be the dimension specifier. In such a case, read the //next word after that. string nextWord = GetNextWord(tokens); if (nextWord.Equals("XYZ")) //NOXLATE { nextWord = GetNextWord(tokens); } else if (nextWord.Equals("XYM")) //NOXLATE { nextWord = GetNextWord(tokens); } else if (nextWord.Equals("XYZM")) //NOXLATE { nextWord = GetNextWord(tokens); } else if (nextWord.Equals("ZM")) //NOXLATE { nextWord = GetNextWord(tokens); } if (nextWord.Equals("EMPTY") || nextWord.Equals("(")) //NOXLATE return nextWord; throw new ParseException(string.Format(Strings.ErrorParseExpectedEmpty, 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(")")) //NOXLATE return nextWord; throw new ParseException(string.Format(Strings.ErrorParseExpectedCloserOrComma, 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(")")) //NOXLATE return nextWord; throw new ParseException(string.Format(Strings.ErrorParseExpectedCloser, 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(Strings.ErrorParseExpectedNumberEos); else if (token is EolToken) throw new ParseException(Strings.ErrorParseExpectedNumberEol); else if (token is FloatToken || token is IntToken) throw new ParseException(string.Format(Strings.ErrorParseExpectedWord, token.StringValue)); else if (token is WordToken) return token.StringValue.ToUpper(); else if (token.StringValue == "(") //NOXLATE return "("; //NOXLATE else if (token.StringValue == ")") //NOXLATE return ")"; //NOXLATE else if (token.StringValue == ",") //NOXLATE return ","; //NOXLATE 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 = null; string sridValue = null; string type = tokens[0].ToString(); if (type == "SRID") //NOXLATE { 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")) //NOXLATE returned = ReadPointText(tokens); else if (type.Equals("LINESTRING")) //NOXLATE returned = ReadLineStringText(tokens); else if (type.Equals("LINEARRING")) //NOXLATE returned = ReadLinearRingText(tokens); else if (type.Equals("POLYGON")) //NOXLATE returned = ReadPolygonText(tokens); else if (type.Equals("MULTIPOINT")) //NOXLATE returned = ReadMultiPointText(tokens); else if (type.Equals("MULTILINESTRING")) //NOXLATE returned = ReadMultiLineStringText(tokens); else if (type.Equals("MULTIPOLYGON")) //NOXLATE returned = ReadMultiPolygonText(tokens); else if (type.Equals("GEOMETRYCOLLECTION")) //NOXLATE returned = ReadGeometryCollectionText(tokens); else throw new ParseException(string.Format(Strings.ErrorParseUnknownType, type)); if (returned == null) throw new NullReferenceException(Strings.ErrorParseGeometryRead); 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")) //NOXLATE 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")) //NOXLATE return geometryFactory.CreatePolygon( geometryFactory.CreateLinearRing(new ICoordinate[] { }), new ILinearRing[] { }); List holes = new List(); ILinearRing shell = ReadLinearRingText(tokens); nextToken = GetNextCloserOrComma(tokens); while (nextToken.Equals(",")) //NOXLATE { 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")) //NOXLATE return geometryFactory.CreateMultiLineString(new ILineString[] { }); List lineStrings = new List(); ILineString lineString = ReadLineStringText(tokens); lineStrings.Add(lineString); nextToken = GetNextCloserOrComma(tokens); while (nextToken.Equals(",")) //NOXLATE { 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")) //NOXLATE return geometryFactory.CreateMultiPolygon(new IPolygon[] { }); List polygons = new List(); IPolygon polygon = ReadPolygonText(tokens); polygons.Add(polygon); nextToken = GetNextCloserOrComma(tokens); while (nextToken.Equals(",")) //NOXLATE { 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")) //NOXLATE return geometryFactory.CreateGeometryCollection(new IGeometry[] { }); List geometries = new List(); IGeometry geometry = ReadGeometryTaggedText(tokens); geometries.Add(geometry); nextToken = GetNextCloserOrComma(tokens); while (nextToken.Equals(",")) //NOXLATE { geometry = ReadGeometryTaggedText(tokens); geometries.Add(geometry); nextToken = GetNextCloserOrComma(tokens); } return geometryFactory.CreateGeometryCollection(geometries.ToArray()); } } }