using System; using System.Collections; using System.IO; using GeoAPI.Geometries; using GisSharpBlog.NetTopologySuite.Algorithm; using GisSharpBlog.NetTopologySuite.Geometries; namespace GisSharpBlog.NetTopologySuite.IO.Handlers { /// /// Converts a Shapefile point to a OGIS Polygon. /// public class PolygonHandler : ShapeHandler { /// /// The ShapeType this handler handles. /// public override ShapeGeometryType ShapeType { get { return ShapeGeometryType.Polygon; } } /// /// Reads a stream and converts the shapefile record to an equilivent geometry object. /// /// The stream to read. /// The geometry factory to use when making the object. /// The Geometry object that represents the shape file record. public override IGeometry Read(BigEndianBinaryReader file, IGeometryFactory geometryFactory) { int shapeTypeNum = file.ReadInt32(); type = (ShapeGeometryType) Enum.Parse(typeof(ShapeGeometryType), shapeTypeNum.ToString()); if (type == ShapeGeometryType.NullShape) return geometryFactory.CreatePolygon(null, null); if (!(type == ShapeGeometryType.Polygon || type == ShapeGeometryType.PolygonM || type == ShapeGeometryType.PolygonZ || type == ShapeGeometryType.PolygonZM)) throw new ShapefileException("Attempting to load a non-polygon as polygon."); // Read and for now ignore bounds. int bblength = GetBoundingBoxLength(); bbox = new double[bblength]; for (; bbindex < 4; bbindex++) { double d = file.ReadDouble(); bbox[bbindex] = d; } int[] partOffsets; int numParts = file.ReadInt32(); int numPoints = file.ReadInt32(); partOffsets = new int[numParts]; for (int i = 0; i < numParts; i++) partOffsets[i] = file.ReadInt32(); ArrayList shells = new ArrayList(); ArrayList holes = new ArrayList(); int start, finish, length; for (int part = 0; part < numParts; part++) { start = partOffsets[part]; if (part == numParts - 1) finish = numPoints; else finish = partOffsets[part + 1]; length = finish - start; CoordinateList points = new CoordinateList(); points.Capacity = length; for (int i = 0; i < length; i++) { ICoordinate external = new Coordinate(file.ReadDouble(), file.ReadDouble() ); geometryFactory.PrecisionModel.MakePrecise( external); ICoordinate internalCoord = external; // Thanks to Abhay Menon! if (!Double.IsNaN(internalCoord.Y) && !Double.IsNaN(internalCoord.X)) points.Add(internalCoord, false); } if (points.Count > 2) // Thanks to Abhay Menon! { if (points[0].Distance(points[points.Count - 1]) > .00001) points.Add(new Coordinate(points[0])); else if (points[0].Distance(points[points.Count - 1]) > 0.0) points[points.Count - 1].CoordinateValue = points[0]; ILinearRing ring = geometryFactory.CreateLinearRing(points.ToArray()); // If shape have only a part, jump orientation check and add to shells if (numParts == 1) shells.Add(ring); else { // Orientation check if (CGAlgorithms.IsCCW(points.ToArray())) holes.Add(ring); else shells.Add(ring); } } } // Now we have a list of all shells and all holes ArrayList holesForShells = new ArrayList(shells.Count); for (int i = 0; i < shells.Count; i++) holesForShells.Add(new ArrayList()); // Find holes for (int i = 0; i < holes.Count; i++) { ILinearRing testRing = (ILinearRing) holes[i]; ILinearRing minShell = null; IEnvelope minEnv = null; IEnvelope testEnv = testRing.EnvelopeInternal; ICoordinate testPt = testRing.GetCoordinateN(0); ILinearRing tryRing; for (int j = 0; j < shells.Count; j++) { tryRing = (ILinearRing) shells[j]; IEnvelope tryEnv = tryRing.EnvelopeInternal; if (minShell != null) minEnv = minShell.EnvelopeInternal; bool isContained = false; CoordinateList coordList = new CoordinateList(tryRing.Coordinates); if (tryEnv.Contains(testEnv) && (CGAlgorithms.IsPointInRing(testPt, coordList.ToArray()) || (PointInList(testPt, coordList)))) isContained = true; // Check if this new containing ring is smaller than the current minimum ring if (isContained) { if (minShell == null || minEnv.Contains(tryEnv)) minShell = tryRing; // Suggested by Brian Macomber and added 3/28/2006: // holes were being found but never added to the holesForShells array // so when converted to geometry by the factory, the inner rings were never created. ArrayList holesForThisShell = (ArrayList) holesForShells[j]; holesForThisShell.Add(testRing); } } } IPolygon[] polygons = new IPolygon[shells.Count]; for (int i = 0; i < shells.Count; i++) polygons[i] = (geometryFactory.CreatePolygon((ILinearRing) shells[i], (ILinearRing[]) ((ArrayList) holesForShells[i]).ToArray(typeof(ILinearRing)))); if (polygons.Length == 1) geom = polygons[0]; else geom = geometryFactory.CreateMultiPolygon(polygons); GrabZMValues(file); return geom; } /// /// Writes a Geometry to the given binary wirter. /// /// The geometry to write. /// The file stream to write to. /// The geometry factory to use. public override void Write(IGeometry geometry, BinaryWriter file, IGeometryFactory geometryFactory) { // This check seems to be not useful and slow the operations... // if (!geometry.IsValid) // Trace.WriteLine("Invalid polygon being written."); IGeometryCollection multi; if (geometry is IGeometryCollection) multi = (IGeometryCollection) geometry; else { GeometryFactory gf = new GeometryFactory(geometry.PrecisionModel); multi = gf.CreateMultiPolygon(new IPolygon[] { (IPolygon) geometry, } ); } file.Write(int.Parse(Enum.Format(typeof(ShapeGeometryType), ShapeType, "d"))); IEnvelope box = multi.EnvelopeInternal; IEnvelope bounds = GetEnvelopeExternal(geometryFactory.PrecisionModel, box); file.Write(bounds.MinX); file.Write(bounds.MinY); file.Write(bounds.MaxX); file.Write(bounds.MaxY); int numParts = GetNumParts(multi); int numPoints = multi.NumPoints; file.Write(numParts); file.Write(numPoints); // write the offsets to the points int offset = 0; for (int part = 0; part < multi.NumGeometries; part++) { // offset to the shell points IPolygon polygon = (IPolygon) multi.Geometries[part]; file.Write(offset); offset = offset + polygon.ExteriorRing.NumPoints; // offstes to the holes foreach (ILinearRing ring in polygon.InteriorRings) { file.Write(offset); offset = offset + ring.NumPoints; } } // write the points for (int part = 0; part < multi.NumGeometries; part++) { IPolygon poly = (IPolygon) multi.Geometries[part]; ICoordinate[] points = poly.ExteriorRing.Coordinates; WriteCoords(new CoordinateList(points), file, geometryFactory); foreach(ILinearRing ring in poly.InteriorRings) { ICoordinate[] points2 = ring.Coordinates; WriteCoords(new CoordinateList(points2), file, geometryFactory); } } } /// /// /// /// /// /// private void WriteCoords(CoordinateList points, BinaryWriter file, IGeometryFactory geometryFactory) { ICoordinate external; foreach (ICoordinate point in points) { // external = geometryFactory.PrecisionModel.ToExternal(point); external = point; file.Write(external.X); file.Write(external.Y); } } /// /// Gets the length of the shapefile record using the geometry passed in. /// /// The geometry to get the length for. /// The length in bytes this geometry is going to use when written out as a shapefile record. public override int GetLength(IGeometry geometry) { int numParts = GetNumParts(geometry); return (22 + (2 * numParts) + geometry.NumPoints * 8); // 22 => shapetype(2) + bbox(4*4) + numparts(2) + numpoints(2) } /// /// /// /// /// private int GetNumParts(IGeometry geometry) { int numParts = 0; if (geometry is IMultiPolygon) { IMultiPolygon mpoly = geometry as IMultiPolygon; foreach (IPolygon poly in mpoly.Geometries) numParts = numParts + poly.InteriorRings.Length + 1; } else if (geometry is IPolygon) numParts = ((IPolygon) geometry).InteriorRings.Length + 1; else throw new InvalidOperationException("Should not get here."); return numParts; } /// /// Test if a point is in a list of coordinates. /// /// TestPoint the point to test for. /// PointList the list of points to look through. /// true if testPoint is a point in the pointList list. private bool PointInList(ICoordinate testPoint, CoordinateList pointList) { foreach(ICoordinate p in pointList) if (p.Equals2D(testPoint)) return true; return false; } } }