Getting geospatial data from shapefiles #2

In my last post we were able to read the header.  This time, we’ll actually drill in and get some actual map data.  To start reading the individual records in the file we’ll need some more data structures. All of the shape records in the spec appear to have this form:

      // Copyright Microsoft Corp. All rights reserved.
// This code is provided AS-IS implies no warranties and confreres no rights
class ShapeRecord
{
public Int32 RecordNumber { get; set; }
public Int32 ContentLength { get; set; }
public ShapeType Shape { get; set; }

        public static ShapeRecord FromBinaryReader(BinaryReader reader)
{
ShapeRecord Record = new ShapeRecord();

            Record.RecordNumber = BinaryUtilities.ReverseBytes(reader.ReadInt32());
Record.ContentLength = BinaryUtilities.ReverseBytes(reader.ReadInt32());
Record.Shape = (ShapeType)reader.ReadInt32();

            return Record;
}
}

So, we can safely read this data in for each record, and then based on the shape type, we can read the rest of the record.  In the case of the state boundary map that we are working with (statesp020.tar.gz.), all the shapes are polygons, and so we can limit our discussion to those. 

I’ve chosen to have a separate data structure for the bounding box and the point.  So we’ll need those structures first:

           // Copyright Microsoft Corp. All rights reserved.
// This code is provided AS-IS implies no warranties and confreres no rights
struct Point
{
public Double X { get; set; }
public Double Y { get; set; }

        public Point(Double x, Double y)
: this()
{
this.X = x;
this.Y = y;
}

        public Point(System.Windows.Point point)
: this()
{
this.X = point.X;
this.Y = point.Y;
}

        public System.Windows.Point ToPoint()
{
return new System.Windows.Point(this.X, this.Y);
}

        public static Point FromBinaryReader(BinaryReader reader)
{
Point point = new Point();

            point.X = reader.ReadDouble();
point.Y = reader.ReadDouble();

            return point;
}

        public static explicit operator Point(System.Windows.Point point)
{
Point myPoint = new Point(point);
return myPoint;
}
}

    struct BoundingBox
{
public Double XMin { get; set; }
public Double YMin { get; set; }
public Double XMax { get; set; }
public Double YMax { get; set; }

        public static BoundingBox FromBinaryReader(BinaryReader reader)
{
BoundingBox box = new BoundingBox();

            box.XMin = reader.ReadDouble();
box.YMin = reader.ReadDouble();
box.XMax = reader.ReadDouble();
box.YMax = reader.ReadDouble();

            return box;
}
}

Now that we’ve got the structures to hold the polygon records, we just need to read them in.  Here’s a snippet that will do nicely:

    // Copyright Microsoft Corp. All rights reserved.
// This code is provided AS-IS implies no warranties and confreres no rights
shapes = new List<ShapeRecord>();

try
{
while (true)
{
ShapeRecord record;
try
{
record = ShapeRecord.FromBinaryReader(reader);
}
catch( System.IO.EndOfStreamException )
{
break;
}
switch (record.Shape)
{
case ShapeType.Polygon:
{
PolygonRecord shape = PolygonRecord.FromBinaryReader(record, reader);
shapes.Add(shape);
break;
}

default:
throw new InvalidDataException("Shape Type Not Recognized");
}
}
}
finally
{
if (file != null)
{
file.Close();
}
}

We’ve got the preliminaries out of the way now. We’ve got a nice map file to work with and tens of thousands of geospatial points.  From here we can have lots of fun.   In the future you can expect posts on displaying geospatial data, map projections, 2D transformations.  This is going to be a hoot!