context: // WFS provider by Peter Robineau (peter.robineau@gmx.at)
  // This file can be redistributed and/or modified under the terms of the GNU Lesser General Public License.
  
  using System;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.Diagnostics;
  using System.Globalization;
  using System.Linq;
  using System.Xml;
  using SharpMap.Data;
  using GeoAPI.Geometries;
  
  namespace SharpMap.Utilities.Wfs
  {
      /// <summary>
      /// This class is the base class for geometry production.
      /// It provides some parsing routines for XML compliant to GML2/GML3.
      /// </summary>
      internal abstract class GeometryFactory : IDisposable
      {
          #region Fields
  
          protected const string _GMLNS = "http://www.opengis.net/gml";
          private readonly NumberFormatInfo _formatInfo = new NumberFormatInfo();
          private readonly HttpClientUtil _httpClientUtil;
          private readonly List<IPathNode> _pathNodes = new List<IPathNode>();
          private int[] _axisOrder;
          protected AlternativePathNodesCollection _CoordinatesNode;
          private string _Cs;
          protected IPathNode _FeatureNode;
          protected XmlReader _FeatureReader;
          protected WfsFeatureTypeInfo _FeatureTypeInfo;
          protected XmlReader _GeomReader;
  
          protected Collection<IGeometry> _Geoms = new Collection<IGeometry>();
  
          protected FeatureDataTable _LabelInfo;
          protected IPathNode _LabelNode;
          protected AlternativePathNodesCollection _ServiceExceptionNode;
          private string _Ts;
          protected XmlReader _XmlReader;
          #endregion
  
          #region Constructors
  
          /// <summary>
          /// Protected constructor for the abstract class.
          /// </summary>
          /// <param name="httpClientUtil">A configured <see cref="HttpClientUtil"/> instance for performing web requests</param>
          /// <param name="featureTypeInfo">A <see cref="WfsFeatureTypeInfo"/> instance providing metadata of the featuretype to query</param>
          /// <param name="labelInfo">A FeatureDataTable for labels</param>
          protected GeometryFactory(HttpClientUtil httpClientUtil, WfsFeatureTypeInfo featureTypeInfo,
                                    FeatureDataTable labelInfo)
          {
              _FeatureTypeInfo = featureTypeInfo;
              Factory = featureTypeInfo.Factory;
              _httpClientUtil = httpClientUtil;
              createReader(httpClientUtil);
  
              try
              {
                  if (labelInfo != null)
                  {
                      _LabelInfo = labelInfo;
                      var pathNodes = new IPathNode[labelInfo.Columns.Count];
                      for (var i = 0; i < pathNodes.Length; i++)
                      {
                          pathNodes[i] = new PathNode(_FeatureTypeInfo.FeatureTypeNamespace, _LabelInfo.Columns[i].ColumnName, (NameTable)_XmlReader.NameTable);
                      }
                      _LabelNode = new AlternativePathNodesCollection(pathNodes);
                  }
              }
              catch (Exception ex)
              {
                  Trace.TraceError("An exception occured while initializing the label path node!");
                  throw ex;
              }
  
              initializePathNodes();
              initializeSeparators();
          }
  
          /// <summary>
          /// Protected constructor for the abstract class.
          /// </summary>
          /// <param name="xmlReader">An XmlReader instance</param>
          /// <param name="featureTypeInfo">A <see cref="WfsFeatureTypeInfo"/> instance providing metadata of the featuretype to query</param>
          protected GeometryFactory(XmlReader xmlReader, WfsFeatureTypeInfo featureTypeInfo)
          {
              _FeatureTypeInfo = featureTypeInfo;
              Factory = featureTypeInfo.Factory;
              _XmlReader = xmlReader;
              initializePathNodes();
              initializeSeparators();
          }
  
          #endregion
  
          internal IGeometryFactory Factory { get; set; }
  
          /// <summary>
          /// Gets or sets the axis order
          /// </summary>
          internal int[] AxisOrder
          {
              get { return _axisOrder; }
              set { _axisOrder = value; }
          }
  
          #region Internal Member
  
  
  
          /// <summary>
          /// Abstract method - overwritten by derived classes for producing instances
          /// derived from <see cref="GeoAPI.Geometries.IGeometry"/>.
          /// </summary>
          internal abstract Collection<IGeometry> createGeometries();
  
          /// <summary>
          /// This method parses quickly without paying attention to
          /// context validation, polygon boundaries and multi-geometries.
          /// This accelerates the geometry parsing process, 
          /// but in scarce cases can lead to errors. 
          /// </summary>
          /// <param name="geometryType">The geometry type (Point, LineString, Polygon, MultiPoint, MultiCurve, 
          /// MultiLineString (deprecated), MultiSurface, MultiPolygon (deprecated)</param>
          /// <returns>The created geometries</returns>
          internal virtual Collection<IGeometry> createQuickGeometries(string geometryType)
          {
              // Ignore multi-geometries
              if (geometryType.Equals("MultiPointPropertyType")) geometryType = "PointPropertyType";
              else if (geometryType.Equals("MultiLineStringPropertyType")) geometryType = "LineStringPropertyType";
              else if (geometryType.Equals("MultiPolygonPropertyType")) geometryType = "PolygonPropertyType";
              else if (geometryType.Equals("MultiCurvePropertyType")) geometryType = "CurvePropertyType";
              else if (geometryType.Equals("MultiSurfacePropertyType")) geometryType = "SurfacePropertyType";
  
              string serviceException = null;
  
              while (_XmlReader.Read())
              {
                  if (_CoordinatesNode.Matches(_XmlReader))
                  {
                      try
                      {
                          switch (geometryType)
                          {
                              case "PointPropertyType":
                                  _Geoms.Add(Factory.CreatePoint(ParseCoordinates(_XmlReader.ReadSubtree())[0]));
                                  break;
                              case "LineStringPropertyType":
                              case "CurvePropertyType":
                                  _Geoms.Add(Factory.CreateLineString(ParseCoordinates(_XmlReader.ReadSubtree())));
                                  break;
                              case "PolygonPropertyType":
                              case "SurfacePropertyType":
                                  _Geoms.Add(Factory.CreatePolygon(
                                      Factory.CreateLinearRing(ParseCoordinates(_XmlReader.ReadSubtree())), null));
                                  break;
                              default:
                                  break;
                          }
                      }
                      catch (Exception ex)
                      {
                          Trace.TraceError("An exception occured while parsing a " + geometryType + " geometry: " +
                                           ex.Message);
                          throw ex;
                      }
                      continue;
                  }
  
                  if (_ServiceExceptionNode.Matches(_XmlReader))
                  {
                      serviceException = _XmlReader.ReadInnerXml();
                      Trace.TraceError("A service exception occured: " + serviceException);
                      throw new Exception("A service exception occured: " + serviceException);
                  }
              }
  
              return _Geoms;
          }
  
          #endregion
  
          #region Protected Member
  
          /// <summary>
          /// This method parses a coordinates or posList(from 'GetFeature' response). 
          /// </summary>
          /// <param name="reader">An XmlReader instance at the position of the coordinates to read</param>
          /// <returns>A point collection (the collected coordinates)</returns>
          protected Coordinate[] ParseCoordinates(XmlReader reader)
          {
              if (!reader.Read()) return null;
  
              string name = reader.LocalName;
              string coordinateString = reader.ReadElementString();
              var vertices = new List<Coordinate>();
              string[][] coordinateValues;
              int i = 0, length = 0;
  
              if (name.Equals("coordinates"))
              {
                  var coords = coordinateString.Split(_Ts[0]);
                  coordinateValues = coords.Select(s => s.Split(_Cs[0])).ToArray();
              }
              else
              {
                  // we assume there are only x,y pairs
                  var coords = coordinateString.Split(' ');
                  var odds = coords.Where((s, idx) => idx%2 == 0);
                  var evens = coords.Where((s, idx) => idx%2 != 0);
                  coordinateValues = (from o in odds
                      from e in evens
                      select new [] {o, e}).ToArray();
              }
              length = coordinateValues.Length;
  
              while (i < length)
              {
                  var c = new double[2];
                  var values = coordinateValues[i++];
                  c[_axisOrder[0]] = Convert.ToDouble(values[0], _formatInfo);
                  c[_axisOrder[1]] = Convert.ToDouble(values[1], _formatInfo);
  
                  var coordinate = values.Length > 2
                      ? new Coordinate(c[0], c[1], Convert.ToDouble(values[2]))
                      : new Coordinate(c[0], c[1]);
                      
                  vertices.Add(coordinate);
              }
  
              return vertices.ToArray();
          }
  
          /// <summary>
          /// This method retrieves an XmlReader within a specified context.
          /// </summary>
          /// <param name="reader">An XmlReader instance that is the origin of a created sub-reader</param>
          /// <param name="labels">A dictionary for recording label values. Pass 'null' to ignore searching for label values</param>
          /// <param name="pathNodes">A list of <see cref="IPathNode"/> instances defining the context of the retrieved reader</param>
          /// <returns>A sub-reader of the XmlReader given as argument</returns>
          protected XmlReader GetSubReaderOf(XmlReader reader, Dictionary<string, string> labels, params IPathNode[] pathNodes)
          {
              _pathNodes.Clear();
              _pathNodes.AddRange(pathNodes);
              return GetSubReaderOf(reader, labels, _pathNodes);
          }
  
          /// <summary>
          /// This method retrieves an XmlReader within a specified context.
          /// Moreover it collects label values before or after a geometry could be found.
          /// </summary>
          /// <param name="reader">An XmlReader instance that is the origin of a created sub-reader</param>
          /// <param name="labels">A dictionary for recording label values. Pass 'null' to ignore searching for label values</param>
          /// <param name="pathNodes">A list of <see cref="IPathNode"/> instances defining the context of the retrieved reader</param>
          /// <returns>A sub-reader of the XmlReader given as argument</returns>
          protected XmlReader GetSubReaderOf(XmlReader reader, Dictionary<string, string> labels, List<IPathNode> pathNodes)
          {
              string errorMessage = null;
  
              while (reader.Read())
              {
                  if (reader.NodeType == XmlNodeType.Element)
                  {
                      if (pathNodes[0].Matches(reader))
                      {
                          pathNodes.RemoveAt(0);
  
                          if (pathNodes.Count > 0)
                              return GetSubReaderOf(reader.ReadSubtree(), null, pathNodes);
  
                          return reader.ReadSubtree();
                      }
  
                      if (labels != null)
                          if (_LabelNode != null)
                              if (_LabelNode.Matches(reader))
                              {
                                  var labelName = reader.Name;
                                  var labelValue = reader.ReadString();
  
                                  // remove the namespace
                                  if (labelName.Contains(":"))
                                      labelName = labelName.Split(':')[1];
  
                                  labels.Add(labelName, labelValue);
                              }
  
  
                      if (_ServiceExceptionNode.Matches(reader))
                      {
                          errorMessage = reader.ReadInnerXml();
                          Trace.TraceError("A service exception occured: " + errorMessage);
                          throw new Exception("A service exception occured: " + errorMessage);
                      }
                  }
              }
  
              return null;
          }
  
          /// <summary>
          /// This method adds labels to the collection.
          /// </summary>
          protected void AddLabel(Dictionary<string, string> labelValues, IGeometry geom)
          {
              if (_LabelInfo == null || geom == null || labelValues == null) return;
  
              try
              {
  
gt:                 FeatureDataRow row = _LabelInfo.NewRow();