/// <summary> /// Creates a new "point" shape that has only the one point. /// </summary> /// <param name="v"></param> public ShapeRange(Vertex v) { FeatureType = FeatureTypes.Point; Parts = new List<PartRange>(); _numParts = -1; double[] coords = new double[2]; coords[0] = v.X; coords[1] = v.Y; PartRange prt = new PartRange(coords, 0,0, FeatureTypes.Point); prt.NumVertices = 1; Parts.Add(prt); }
/// <summary> /// Creates a shape based on the specified feature. This shape will be standing alone, /// all by itself. The fieldnames and field types will be null. /// </summary> /// <param name="feature"></param> public Shape(IFeature feature) { _shapeRange = new ShapeRange(feature.FeatureType); _attributes = feature.DataRow.ItemArray; IList<Coordinate> coords = feature.Coordinates; _vertices = new double[feature.NumPoints*2]; _z = new double[feature.NumPoints]; _m = new double[feature.NumPoints]; for (int i = 0; i < coords.Count; i++) { Coordinate c = coords[i]; _vertices[i*2] = c.X; _vertices[i*2 + 1] = c.Y; _z[i] = c.Z; _m[i] = c.M; } int offset = 0; for(int ig = 0; ig < feature.NumGeometries; ig++) { IBasicGeometry g = feature.GetBasicGeometryN(ig); PartRange prt = new PartRange(_vertices, 0, offset, feature.FeatureType); _shapeRange.Parts.Add(prt); offset += g.NumPoints; } }
/// <summary> /// Obtains a typed list of ShapefilePoint structures with double values associated with the various coordinates. /// </summary> /// <param name="filename">A string filename</param> /// <param name="progressHandler">A progress indicator</param> private void FillPoints(string filename, IProgressHandler progressHandler) { // Check to ensure the filename is not null if (filename == null) { throw new NullReferenceException(MessageStrings.ArgumentNull_S.Replace("%S",filename)); } if (File.Exists(filename) == false) { throw new FileNotFoundException(MessageStrings.FileNotFound_S.Replace("%S", filename)); } // Reading the headers gives us an easier way to track the number of shapes and their overall length etc. List<ShapeHeader> shapeHeaders = ReadIndexFile(filename); // Get the basic header information. ShapefileHeader header = new ShapefileHeader(filename); Extent = new Extent(new[] { header.Xmin, header.Ymin, header.Xmax, header.Ymax }); Envelope = Extent.ToEnvelope(); // Check to ensure that the filename is the correct shape type if (header.ShapeType != ShapeTypes.Point && header.ShapeType != ShapeTypes.PointM && header.ShapeType != ShapeTypes.PointZ) { throw new ApplicationException(MessageStrings.FileNotPoints_S.Replace("%S", filename)); } // This will set up a reader so that we can read values in huge chunks, which is much faster than one value at a time. IO.BufferedBinaryReader bbReader = new IO.BufferedBinaryReader(filename, progressHandler); if (bbReader.FileLength == 100) { bbReader.Close(); // the file is empty so we are done reading return; } // Skip the shapefile header by skipping the first 100 bytes in the shapefile bbReader.Seek(100, SeekOrigin.Begin); int numShapes = shapeHeaders.Count; byte[] bigEndian = new byte[numShapes * 8]; byte[] allCoords = new byte[numShapes * 16]; bool isM = false; bool isZ = false; if(header.ShapeType == ShapeTypes.PointM || header.ShapeType == ShapeTypes.PointZ) { isM = true; } if (header.ShapeType == ShapeTypes.PointZ) { isZ = true; } byte[] allM = new byte[8]; if(isM) allM = new byte[numShapes * 8]; byte[] allZ = new byte[8]; if(isZ) allZ = new byte[numShapes * 8]; for (int shp = 0; shp < numShapes; shp++) { // Read from the index file because some deleted records // might still exist in the .shp file. long offset = (shapeHeaders[shp].ByteOffset); bbReader.Seek(offset, SeekOrigin.Begin); bbReader.Read(bigEndian, shp * 8, 8); ShapeTypes type = (ShapeTypes)bbReader.ReadInt32(); //bbReader.Seek(4, SeekOrigin.Current); bbReader.Read(allCoords, shp * 16, 16); if(isZ) { bbReader.Read(allZ, shp*8, 8); } if (isM) { bbReader.Read(allM, shp*8, 8); } ShapeRange shape = new ShapeRange(FeatureTypes.Point); shape.StartIndex = shp; shape.ContentLength = 8; shape.NumPoints = 1; shape.NumParts = 1; ShapeIndices.Add(shape); } double[] vert = new double[2 * numShapes]; Buffer.BlockCopy(allCoords, 0, vert, 0, numShapes*16); Vertex = vert; if(isM) { double[] m = new double[numShapes]; Buffer.BlockCopy(allM, 0, m, 0, numShapes * 8); M = m; } if(isZ) { double[] z = new double[numShapes]; Buffer.BlockCopy(allZ, 0, z, 0, numShapes * 8); Z = z; } for (int shp = 0; shp < numShapes; shp++) { PartRange part = new PartRange(vert, shp, 0, FeatureTypes.Point); part.NumVertices = 1; ShapeRange shape = ShapeIndices[shp]; shape.Parts.Add(part); shape.Extent = new Extent(new[] {vert[shp*2], vert[shp*2 + 1], vert[shp*2], vert[shp*2 + 1]}); } bbReader.Dispose(); }
private static void ReadMultiPolygon(Stream data, FeatureSetPack results) { int numPolygons = ReadInt32(data); ShapeRange lShp = new ShapeRange(FeatureTypes.Polygon); List<double[]> rings = new List<double[]>(); int partOffset = 0; for (int iPoly = 0; iPoly < numPolygons; iPoly++ ) { data.Seek(5, SeekOrigin.Current); // endian and geometry type int numRings = ReadInt32(data); for (int iRing = 0; iRing < numRings; iRing++) { int numPoints = ReadInt32(data); // ring structures are like a linestring without the final point. double[] coords = ReadDouble(data, 2 * numPoints); if (iRing == 0) { // By shapefile standard, the shell should be clockwise if (IsCounterClockwise(coords)) ReverseCoords(coords); } else { // By shapefile standard, the holes should be counter clockwise. if (!IsCounterClockwise(coords)) ReverseCoords(coords); } PartRange lPrt = new PartRange(FeatureTypes.Polygon); lPrt.PartOffset = partOffset; lPrt.NumVertices = numPoints; lShp.Parts.Add(lPrt); partOffset += coords.Length / 2; rings.Add(coords); } } double[] allVertices = new double[partOffset * 2]; int offset = 0; foreach (double[] ring in rings) { Array.Copy(ring, 0, allVertices, offset, ring.Length); offset += ring.Length; } results.Add(allVertices, lShp); }
/// <summary> /// Without changing the feature type or anything else, simply update the local coordinates /// to include the new coordinates. All the new coordinates will be considered one part. /// Since point and multi-point shapes don't have parts, they will just be appended to the /// original part. /// </summary> public void AddPart(IEnumerable<Coordinate> coordinates, CoordinateTypes coordType) { bool hasM = (coordType == CoordinateTypes.M || coordType == CoordinateTypes.Z); bool hasZ = (coordType == CoordinateTypes.Z); List<double> vertices = new List<double>(); List<double> z = new List<double>(); List<double> m = new List<double>(); int numPoints = 0; int oldNumPoints = (_vertices != null) ? _vertices.Length/2: 0; foreach (Coordinate coordinate in coordinates) { if(_shapeRange.Extent == null)_shapeRange.Extent = new Extent(); _shapeRange.Extent.ExpandToInclude(coordinate.X, coordinate.Y); vertices.Add(coordinate.X); vertices.Add(coordinate.Y); if(hasM)m.Add(coordinate.M); if(hasZ)z.Add(coordinate.Z); numPoints++; } // Using public accessor also updates individual part references Vertices = (_vertices != null) ? _vertices.Concat(vertices).ToArray() : vertices.ToArray(); if(hasZ) _z = (_z != null) ? _z.Concat(z).ToArray() : z.ToArray(); if(hasM) _m = (_m != null) ? _m.Concat(m).ToArray() : m.ToArray(); if(_shapeRange.FeatureType == FeatureTypes.MultiPoint || _shapeRange.FeatureType == FeatureTypes.Point) { // Only one part exists _shapeRange.Parts[0].NumVertices += numPoints; } else { PartRange part = new PartRange(_vertices, _shapeRange.StartIndex, oldNumPoints, _shapeRange.FeatureType ); part.NumVertices = numPoints; _shapeRange.Parts.Add(part); } }
/// <summary> /// For each coordinate in the other part, if it falls in the extent of this polygon, a /// ray crossing test is used for point in polygon testing. If it is not in the extent, /// it is skipped. /// </summary> /// <param name="polygonShape">The part of the polygon to analyze polygon</param> /// <param name="otherPart">The other part</param> /// polygonshape and the shape extent is the same as the part extent.</param> /// <returns>Boolean, true if any coordinate falls inside the polygon</returns> private static bool ContainsVertex(ShapeRange polygonShape, PartRange otherPart) { // Create an extent for faster checking in most cases Extent ext = polygonShape.Extent; foreach (Vertex point in otherPart) { // This extent check shortcut should help speed things up for large polygon parts if (!ext.Intersects(point)) continue; // Imagine a ray on the horizontal starting from point.X -> infinity. (In practice this can be ext.XMax) // Count the intersections of segments with that line. If the resulting count is odd, the point is inside. double x1 = point.X; double y1 = point.Y; double x2 = ext.XMax; Segment ray = new Segment(point.X, point.Y, ext.XMax, point.Y); int[] numCrosses = new int[polygonShape.NumParts]; // A cross is a complete cross. Coincident doesn't count because it is either 0 or 2 crosses. int totalCrosses = 0; int iPart = 0; foreach (PartRange ring in polygonShape.Parts) { foreach (Segment segment in ring.Segments) { if(segment.IntersectionCount(ray) != 1) continue; numCrosses[iPart]++; totalCrosses++; } iPart++; } // If we didn't actually have any polygons we cant intersect with anything if (polygonShape.NumParts < 1) return false; // For shapes with only one part, we don't need to test part-containment. if(polygonShape.NumParts == 1 && totalCrosses % 2 == 1) return true; // Regardless of number of parts or holiness, 1 crossing can only be inside. if(totalCrosses == 1) return true; totalCrosses = 0; for (iPart = 0; iPart < numCrosses.Length; iPart++) { int count = numCrosses[iPart]; // If this part does not contain the point, don't bother trying to figure out if the part is a hole or not. if (count % 2 == 0) continue; // If this particular part is a hole, subtract the total crosses by 1, otherwise add one. // This takes time, so we want to do this as few times as possible. if(polygonShape.Parts[iPart].IsHole()) { totalCrosses--; } else { totalCrosses++; } } return totalCrosses > 0; } return false; }
private static Shape ReadPolygon(Stream data) { Shape result = new Shape(FeatureTypes.Polygon); int numRings = ReadInt32(data); List<double[]> rings = new List<double[]>(); int partOffset = 0; for (int iRing = 0; iRing < numRings; iRing++) { int numPoints = ReadInt32(data); // ring structures are like a linestring without the final point. double[] coords = ReadDouble(data, 2 * numPoints); if (iRing == 0) { // By shapefile standard, the shell should be clockwise if (IsCounterClockwise(coords)) ReverseCoords(coords); } else { // By shapefile standard, the holes should be counter clockwise. if (!IsCounterClockwise(coords)) ReverseCoords(coords); } PartRange lPrt = new PartRange(FeatureTypes.Polygon); lPrt.PartOffset = partOffset; lPrt.NumVertices = numPoints; result.Range.Parts.Add(lPrt); partOffset += coords.Length / 2; rings.Add(coords); } double[] allVertices = new double[partOffset * 2]; int offset = 0; foreach (double[] ring in rings) { Array.Copy(ring, 0, allVertices, offset, ring.Length); offset += ring.Length; } result.Vertices = allVertices; return result; }
private static Shape ReadLineString(Stream data) { Shape result = new Shape(FeatureTypes.Line); int count = ReadInt32(data); double[] coords = ReadDouble(data, 2 * count); PartRange lPrt = new PartRange(FeatureTypes.Line); lPrt.NumVertices = count; result.Range.Parts.Add(lPrt); result.Vertices = coords; return result; }
private static void ReadLineString(Stream data, FeatureSetPack results) { int count = ReadInt32(data); double[] coords = ReadDouble(data, 2 * count); ShapeRange lShp = new ShapeRange(FeatureTypes.Line); PartRange lPrt = new PartRange(FeatureTypes.Line); lPrt.NumVertices = count; lShp.Parts.Add(lPrt); results.Add(coords, lShp); }
/// <summary> /// Reads one multipoint shape from a data stream. /// (this assumes that the two bytes (endian and type) have already been read. /// </summary> /// <param name="data"></param> /// <returns></returns> private static Shape ReadMultiPoint(Stream data) { Shape result = new Shape(FeatureTypes.MultiPoint); int count = ReadInt32(data); PartRange prt = new PartRange(FeatureTypes.MultiPoint); prt.NumVertices = count; result.Range.Parts.Add(prt); double[] vertices = new double[count * 2]; for (int iPoint = 0; iPoint < count; iPoint++) { data.ReadByte(); // ignore endian ReadInt32(data); // ignore geometry type double[] coord = ReadDouble(data, 2); Array.Copy(coord, 0, vertices, iPoint * 2, 2); } result.Vertices = vertices; return result; }
private static void ReadMultiPoint(Stream data, FeatureSetPack results) { int count = ReadInt32(data); ShapeRange sr = new ShapeRange(FeatureTypes.MultiPoint); PartRange prt = new PartRange(FeatureTypes.MultiPoint); prt.NumVertices = count; sr.Parts.Add(prt); double[] vertices = new double[count*2]; for(int iPoint = 0; iPoint < count; iPoint++) { data.ReadByte(); // ignore endian ReadInt32(data); // ignore geometry type double[] coord = ReadDouble(data, 2); Array.Copy(coord, 0, vertices, iPoint*2, 2); } results.Add(vertices, sr); }
/// <summary> /// This assumes that the byte order and shapetype have already been read. /// </summary> /// <param name="data"></param> /// <param name="results"></param> private static void ReadPoint(Stream data, FeatureSetPack results) { ShapeRange sr = new ShapeRange(FeatureTypes.MultiPoint); PartRange prt = new PartRange(FeatureTypes.MultiPoint); prt.NumVertices = 1; sr.Parts.Add(prt); double[] coord = ReadDouble(data, 2); results.Add(coord, sr); }
/// <summary> /// This assumes that the byte order and shapetype have already been read. /// </summary> /// <param name="data"></param> public static Shape ReadPoint(Stream data) { Shape result = new Shape(); result.Range = new ShapeRange(FeatureTypes.Point); PartRange prt = new PartRange(FeatureTypes.Point); prt.NumVertices = 1; result.Range.Parts.Add(prt); result.Vertices = ReadDouble(data, 2); return result; }
// X Y MultiPoints: Total Length = 28 Bytes // --------------------------------------------------------- // Position Value Type Number Byte Order // --------------------------------------------------------- // Byte 0 Record Number Integer 1 Big // Byte 4 Content Length Integer 1 Big // Byte 8 Shape Type 8 Integer 1 Little // Byte 12 Xmin Double 1 Little // Byte 20 Ymin Double 1 Little // Byte 28 Xmax Double 1 Little // Byte 36 Ymax Double 1 Little // Byte 48 NumPoints Integer 1 Little // Byte X Points Point NumPoints Little // X Y M MultiPoints: Total Length = 34 Bytes // --------------------------------------------------------- // Position Value Type Number Byte Order // --------------------------------------------------------- // Byte 0 Record Number Integer 1 Big // Byte 4 Content Length Integer 1 Big // Byte 8 Shape Type 28 Integer 1 Little // Byte 12 Box Double 4 Little // Byte 44 NumPoints Integer 1 Little // Byte X Points Point NumPoints Little // Byte Y* Mmin Double 1 Little // Byte Y + 8* Mmax Double 1 Little // Byte Y + 16* Marray Double NumPoints Little // X Y Z M MultiPoints: Total Length = 44 Bytes // --------------------------------------------------------- // Position Value Type Number Byte Order // --------------------------------------------------------- // Byte 0 Record Number Integer 1 Big // Byte 4 Content Length Integer 1 Big // Byte 8 Shape Type 18 Integer 1 Little // Byte 12 Box Double 4 Little // Byte 44 NumPoints Integer 1 Little // Byte X Points Point NumPoints Little // Byte Y Zmin Double 1 Little // Byte Y + 8 Zmax Double 1 Little // Byte Y + 16 Zarray Double NumPoints Little // Byte Z* Mmin Double 1 Little // Byte Z+8* Mmax Double 1 Little // Byte Z+16* Marray Double NumPoints Little private void FillPoints(string filename, IProgressHandler progressHandler) { // Check to ensure the filename is not null if (filename == null) { throw new NullReferenceException(MessageStrings.ArgumentNull_S.Replace("%S", filename)); } if (File.Exists(filename) == false) { throw new FileNotFoundException(MessageStrings.FileNotFound_S.Replace("%S", filename)); } // Get the basic header information. ShapefileHeader header = new ShapefileHeader(filename); Extent = new Extent(new[] { header.Xmin, header.Ymin, header.Xmax, header.Ymax }); // Check to ensure that the filename is the correct shape type if (header.ShapeType != ShapeTypes.MultiPoint && header.ShapeType != ShapeTypes.MultiPointM && header.ShapeType != ShapeTypes.MultiPointZ) { throw new ArgumentException(MessageStrings.FileNotLines_S.Replace("%S", filename)); } // Reading the headers gives us an easier way to track the number of shapes and their overall length etc. List<ShapeHeader> shapeHeaders = ReadIndexFile(filename); // This will set up a reader so that we can read values in huge chunks, which is much faster than one value at a time. IO.BufferedBinaryReader bbReader = new IO.BufferedBinaryReader(filename, progressHandler); if (bbReader.FileLength == 100) { // The shapefile is empty so we can simply return here bbReader.Close(); return; } // Skip the shapefile header by skipping the first 100 bytes in the shapefile bbReader.Seek(100, SeekOrigin.Begin); int numShapes = shapeHeaders.Count; byte[] bigEndians = new byte[numShapes * 8]; byte[] allBounds = new byte[numShapes * 32]; ByteBlock allCoords = new ByteBlock(BLOCKSIZE); bool isM = (header.ShapeType == ShapeTypes.MultiPointZ || header.ShapeType == ShapeTypes.MultiPointM); bool isZ = (header.ShapeType == ShapeTypes.PolyLineZ); ByteBlock allZ = null; ByteBlock allM = null; if (isZ) { allZ = new ByteBlock(BLOCKSIZE); } if (isM) { allM = new ByteBlock(BLOCKSIZE); } int pointOffset = 0; for (int shp = 0; shp < numShapes; shp++) { // Read from the index file because some deleted records // might still exist in the .shp file. long offset = (shapeHeaders[shp].ByteOffset); bbReader.Seek(offset, SeekOrigin.Begin); // time: 200 ms ShapeRange shape = new ShapeRange(FeatureTypes.MultiPoint); shape.RecordNumber = bbReader.ReadInt32(false); // Byte 0 Record Number Integer 1 Big shape.ContentLength = bbReader.ReadInt32(false); // Byte 4 Content Length Integer 1 Big //bbReader.Read(bigEndians, shp * 8, 8); shape.ShapeType = (ShapeTypes)bbReader.ReadInt32(); shape.StartIndex = pointOffset; if (shape.ShapeType == ShapeTypes.NullShape) { continue; } bbReader.Read(allBounds, shp * 32, 32); shape.NumParts = 1; shape.NumPoints = bbReader.ReadInt32(); allCoords.Read(shape.NumPoints * 16, bbReader); pointOffset += shape.NumPoints; if (header.ShapeType == ShapeTypes.MultiPointM) { // These are listed as "optional" but there isn't a good indicator of how to determine if they were added. // To handle the "optional" M values, check the contentLength for the feature. // The content length does not include the 8-byte record header and is listed in 16-bit words. if (shape.ContentLength * 2 > 44 + 4 * shape.NumParts + 16 * shape.NumPoints) { double mMin = bbReader.ReadDouble(); double mMax = bbReader.ReadDouble(); //bbReader.Seek(16, SeekOrigin.Current); if (allM != null) allM.Read(shape.NumPoints * 8, bbReader); } } if (header.ShapeType == ShapeTypes.MultiPointZ) { bool hasM = shape.ContentLength * 2 > 60 + 4 * shape.NumParts + 24 * shape.NumPoints; double zMin = bbReader.ReadDouble(); double zMax = bbReader.ReadDouble(); // For Z shapefiles, the Z part is not optional. if (allZ != null) allZ.Read(shape.NumPoints * 8, bbReader); // These are listed as "optional" but there isn't a good indicator of how to determine if they were added. // To handle the "optional" M values, check the contentLength for the feature. // The content length does not include the 8-byte record header and is listed in 16-bit words. if (hasM) { double mMin = bbReader.ReadDouble(); double mMax = bbReader.ReadDouble(); if (allM != null) allM.Read(shape.NumPoints * 8, bbReader); } } // Now that we have read all the values, create the geometries from the points and parts arrays. ShapeIndices.Add(shape); } double[] vert = allCoords.ToDoubleArray(); Vertex = vert; if (isM) M = allM.ToDoubleArray(); if (isZ) Z = allZ.ToDoubleArray(); Array.Reverse(bigEndians); List<ShapeRange> shapes = ShapeIndices; double[] bounds = new double[numShapes * 4]; Buffer.BlockCopy(allBounds, 0, bounds, 0, allBounds.Length); for (int shp = 0; shp < numShapes; shp++) { ShapeRange shape = shapes[shp]; shape.Extent = new Extent(bounds, shp * 4); int endIndex = shape.NumPoints + shape.StartIndex; int startIndex = shape.StartIndex; int count = endIndex - startIndex; PartRange partR = new PartRange(vert, shape.StartIndex, 0, FeatureTypes.MultiPoint); partR.NumVertices = count; shape.Parts.Add(partR); } GC.Collect(); bbReader.Dispose(); }
/// <summary> /// This creates a polygon shape from an extent. /// </summary> /// <param name="ext">The extent to turn into a polygon shape.</param> public ShapeRange(Extent ext) { Extent = ext; Parts = new List<PartRange>(); _numParts = -1; // Counter clockwise // 1 2 // 4 3 double[] coords = new double[8]; // C1 coords[0] = ext.XMin; coords[1] = ext.YMax; // C2 coords[2] = ext.XMax; coords[3] = ext.YMax; // C3 coords[4] = ext.XMax; coords[5] = ext.YMin; // C4 coords[6] = ext.XMin; coords[7] = ext.YMin; FeatureType = FeatureTypes.Polygon; ShapeType = ShapeTypes.Polygon; PartRange pr = new PartRange(coords, 0,0, FeatureTypes.Polygon); pr.NumVertices = 4; Parts.Add(pr); }
private static void ReadMultiLineString(Stream data, FeatureSetPack results) { int numLineStrings = ReadInt32(data); ShapeRange shp = new ShapeRange(FeatureTypes.Line); List<double[]> strings = new List<double[]>(); int partOffset = 0; for(int iString = 0; iString < numLineStrings; iString++) { // Each of these needs to read a full WKBLineString data.Seek(5, SeekOrigin.Current); //ignore header int numPoints = ReadInt32(data); double[] coords = ReadDouble(data, 2*numPoints); PartRange lPrt = new PartRange(FeatureTypes.Line); lPrt.PartOffset = partOffset; lPrt.NumVertices = numPoints; shp.Parts.Add(lPrt); partOffset += coords.Length/2; strings.Add(coords); } double[] allVertices = new double[partOffset * 2]; int offset = 0; foreach (double[] ring in strings) { Array.Copy(ring, 0, allVertices, offset, ring.Length); offset += ring.Length; } results.Add(allVertices, shp); }
/// <summary> /// Occurs when the vertices are being re-calculated. /// </summary> protected virtual void OnInitializeVertices() { int count = 0; foreach (IFeature f in _features) { count += f.NumPoints; } _vertices = new double[count*2]; int i = 0; foreach(IFeature f in _features) { IList<Coordinate> coords = f.Coordinates; // this should be all the coordinates, for all parts of the geometry. if (coords == null) continue; foreach (Coordinate c in coords) { _vertices[i*2] = c.X; _vertices[i*2+1] = c.Y; // vertexValues.Add(c.Values); // essentially add a reference pointer to the internal array of values i++; } } //_vertices = vertexValues.ToArray(); // not sure, but I bet arrays a smidge faster at indexed access than lists _shapeIndices = new List<ShapeRange>(); int vIndex = 0; for(int shp = 0; shp < _features.Count; shp++) { IFeature f = _features[shp]; ShapeRange shx = new ShapeRange(FeatureType); shx.Extent = new Extent(f.Envelope); _shapeIndices.Add(shx); f.ShapeIndex = shx; // for simplicity in looping, there is always at least one part. // That way, the shape range can be ignored and the parts loop used instead. shx.Parts = new List<PartRange>(); int shapeStart = vIndex; for (int part = 0; part < f.NumGeometries; part++) { PartRange prtx = new PartRange(_vertices, shapeStart, vIndex-shapeStart, FeatureType); IBasicPolygon bp = f.GetBasicGeometryN(part) as IBasicPolygon; if(bp != null) { // Account for the Shell prtx.NumVertices = bp.Shell.NumPoints; vIndex += bp.Shell.NumPoints; // The part range should be adjusted to no longer include the holes foreach (var hole in bp.Holes) { PartRange holex = new PartRange(_vertices, shapeStart, vIndex - shapeStart, FeatureType); holex.NumVertices = hole.NumPoints; shx.Parts.Add(holex); vIndex += hole.NumPoints; } } else { int numPoints = f.GetBasicGeometryN(part).NumPoints; // This is not a polygon, so just add the number of points. vIndex += numPoints; prtx.NumVertices = numPoints; } shx.Parts.Add(prtx); } } // _vertices = vertexValues.ToArray(); _verticesAreValid = true; }
// X Y Poly Lines: Total Length = 28 Bytes // --------------------------------------------------------- // Position Value Type Number Byte Order // --------------------------------------------------------- // Byte 0 Record Number Integer 1 Big // Byte 4 Content Length Integer 1 Big // Byte 8 Shape Type 3 Integer 1 Little // Byte 12 Xmin Double 1 Little // Byte 20 Ymin Double 1 Little // Byte 28 Xmax Double 1 Little // Byte 36 Ymax Double 1 Little // Byte 44 NumParts Integer 1 Little // Byte 48 NumPoints Integer 1 Little // Byte 52 Parts Integer NumParts Little // Byte X Points Point NumPoints Little // X Y M Poly Lines: Total Length = 34 Bytes // --------------------------------------------------------- // Position Value Type Number Byte Order // --------------------------------------------------------- // Byte 0 Record Number Integer 1 Big // Byte 4 Content Length Integer 1 Big // Byte 8 Shape Type 23 Integer 1 Little // Byte 12 Box Double 4 Little // Byte 44 NumParts Integer 1 Little // Byte 48 NumPoints Integer 1 Little // Byte 52 Parts Integer NumParts Little // Byte X Points Point NumPoints Little // Byte Y* Mmin Double 1 Little // Byte Y + 8* Mmax Double 1 Little // Byte Y + 16* Marray Double NumPoints Little // X Y Z M Poly Lines: Total Length = 44 Bytes // --------------------------------------------------------- // Position Value Type Number Byte Order // --------------------------------------------------------- // Byte 0 Record Number Integer 1 Big // Byte 4 Content Length Integer 1 Big // Byte 8 Shape Type 13 Integer 1 Little // Byte 12 Box Double 4 Little // Byte 44 NumParts Integer 1 Little // Byte 48 NumPoints Integer 1 Little // Byte 52 Parts Integer NumParts Little // Byte X Points Point NumPoints Little // Byte Y Zmin Double 1 Little // Byte Y + 8 Zmax Double 1 Little // Byte Y + 16 Zarray Double NumPoints Little // Byte Z* Mmin Double 1 Little // Byte Z+8* Mmax Double 1 Little // Byte Z+16* Marray Double NumPoints Little private void FillPolygons(string filename, IProgressHandler progressHandler) { // Check to ensure the filename is not null if (filename == null) { throw new NullReferenceException(MessageStrings.ArgumentNull_S.Replace("%S", filename)); } if (File.Exists(filename) == false) { throw new FileNotFoundException(MessageStrings.FileNotFound_S.Replace("%S", filename)); } // Get the basic header information. ShapefileHeader header = new ShapefileHeader(filename); Extent = new Extent(new[]{header.Xmin, header.Ymin, header.Xmax, header.Ymax}); // Check to ensure that the filename is the correct shape type if (header.ShapeType != ShapeTypes.Polygon && header.ShapeType != ShapeTypes.PolygonM && header.ShapeType != ShapeTypes.PolygonZ) { throw new ArgumentException(MessageStrings.FileNotLines_S.Replace("%S", filename)); } // Reading the headers gives us an easier way to track the number of shapes and their overall length etc. List<ShapeHeader> shapeHeaders = ReadIndexFile(filename); // This will set up a reader so that we can read values in huge chunks, which is much faster than one value at a time. IO.BufferedBinaryReader bbReader = new IO.BufferedBinaryReader(filename, progressHandler); if (bbReader.FileLength == 100) { // The shapefile is empty so we can simply return here bbReader.Close(); return; } // Skip the shapefile header by skipping the first 100 bytes in the shapefile bbReader.Seek(100, SeekOrigin.Begin); int numShapes = shapeHeaders.Count; int [] partOffsets = new int[numShapes]; byte[] bigEndians = new byte[numShapes * 8]; byte[] allBounds = new byte[numShapes * 32]; ByteBlock allParts = new ByteBlock(BLOCKSIZE); // probably all will be in one block, but use a byteBlock just in case. ByteBlock allCoords = new ByteBlock(BLOCKSIZE); bool isM = (header.ShapeType == ShapeTypes.PolyLineM || header.ShapeType == ShapeTypes.PolyLineZ); bool isZ = (header.ShapeType == ShapeTypes.PolyLineZ); ByteBlock allZ = null; ByteBlock allM = null; if (isZ) { allZ = new ByteBlock(BLOCKSIZE); } if (isM) { allM = new ByteBlock(BLOCKSIZE); } int pointOffset = 0; for (int shp = 0; shp < numShapes; shp++) { // Read from the index file because some deleted records // might still exist in the .shp file. long offset = (shapeHeaders[shp].ByteOffset); bbReader.Seek(offset, SeekOrigin.Begin); // Position Value Type Number Byte Order ShapeRange shape = new ShapeRange(FeatureTypes.Polygon); //-------------------------------------------------------------------- shape.RecordNumber = bbReader.ReadInt32(false); // Byte 0 Record Number Integer 1 Big shape.ContentLength = bbReader.ReadInt32(false); // Byte 4 Content Length Integer 1 Big shape.ShapeType = (ShapeTypes)bbReader.ReadInt32(); // Byte 8 Shape Type Integer 1 Little shape.StartIndex = pointOffset; if (shape.ShapeType == ShapeTypes.NullShape) { continue; } bbReader.Read(allBounds, shp*32, 32); //double xMin = bbReader.ReadDouble(); // Byte 12 Xmin Double 1 Little // double yMin = bbReader.ReadDouble(); // Byte 20 Ymin Double 1 Little //double xMax = bbReader.ReadDouble(); // Byte 28 Xmax Double 1 Little //double yMax = bbReader.ReadDouble(); // Byte 36 Ymax Double 1 Little shape.NumParts = bbReader.ReadInt32(); // Byte 44 NumParts Integer 1 Little //feature.NumPoints = bbReader.ReadInt32(); // Byte 48 NumPoints Integer 1 Little shape.NumPoints = bbReader.ReadInt32(); // Create an envelope from the extents box in the file. //feature.Envelope = new Envelope(xMin, xMax, yMin, yMax); partOffsets[shp] = allParts.IntOffset(); allParts.Read(shape.NumParts * 4, bbReader); allCoords.Read(shape.NumPoints * 16, bbReader); pointOffset += shape.NumPoints; if (header.ShapeType == ShapeTypes.PolygonM) { // These are listed as "optional" but there isn't a good indicator of how to determine if they were added. // To handle the "optional" M values, check the contentLength for the feature. // The content length does not include the 8-byte record header and is listed in 16-bit words. if (shape.ContentLength * 2 > 44 + 4 * shape.NumParts + 16 * shape.NumPoints) { double mMin = bbReader.ReadDouble(); double mMax = bbReader.ReadDouble(); if(allM != null)allM.Read(shape.NumPoints * 8, bbReader); } } if (header.ShapeType == ShapeTypes.PolygonZ) { bool hasM = shape.ContentLength * 2 > 60 + 4 * shape.NumParts + 24 * shape.NumPoints; double zMin = bbReader.ReadDouble(); double zMax = bbReader.ReadDouble(); // For Z shapefiles, the Z part is not optional. if (allZ != null) allZ.Read(shape.NumPoints * 8, bbReader); // These are listed as "optional" but there isn't a good indicator of how to determine if they were added. // To handle the "optional" M values, check the contentLength for the feature. // The content length does not include the 8-byte record header and is listed in 16-bit words. if (hasM) { double mMin = bbReader.ReadDouble(); double mMax = bbReader.ReadDouble(); if (allM != null) allM.Read(shape.NumPoints * 8, bbReader); } } ShapeIndices.Add(shape); } double[] vert = allCoords.ToDoubleArray(); Vertex = vert; if (isM) M = allM.ToDoubleArray(); if (isZ) Z = allZ.ToDoubleArray(); List<ShapeRange> shapes = ShapeIndices; double[] bounds = new double[numShapes * 4]; Buffer.BlockCopy(allBounds, 0, bounds, 0, allBounds.Length); int[] parts = allParts.ToIntArray(); ProgressMeter = new ProgressMeter(ProgressHandler, "Testing Parts and Holes", numShapes); for (int shp = 0; shp < numShapes; shp++) { ShapeRange shape = shapes[shp]; shape.Extent = new Extent(bounds, shp * 4); for (int part = 0; part < shape.NumParts; part++) { int offset = partOffsets[shp]; int endIndex = shape.NumPoints + shape.StartIndex; int startIndex = parts[offset + part] + shape.StartIndex; if (part < shape.NumParts - 1) endIndex = parts[offset + part + 1] + shape.StartIndex; int count = endIndex - startIndex; PartRange partR = new PartRange(vert, shape.StartIndex, parts[offset + part], FeatureTypes.Polygon); partR.NumVertices = count; shape.Parts.Add(partR); } ProgressMeter.CurrentValue = shp; } ProgressMeter.Reset(); GC.Collect(); }