Getting geospatial data from shapefiles

There are lots of digital map files available from the Government.  And in the case of the national atlas the data is stored in shape files.  And, as luck with have it, shape files are easy to read. The ESRI Shapefile Technical Description is easy to find and download.   The shapefile I’m using is statesp020.tar.gz.  You’ll need something that can open a GZip file.  Inside you’ll find the shapefile.

I should also add here that while I am a professional developer, I’m certainly not a professional C# developer.  I live firmly in the world of C++, and not even modern C++.  My code has been described to me as “C with classes”.  But for jobs like this C# is a great too, and through little projects like this I’m trying to make myself more familiar with it.

The code follows fairly easily from the spec.  But there are a few interesting twists. One of them is the need to read bigendian integers.  With C++, this kind of conversion is very straightforward.  I can just do my typical type-unsafe cast and get at the raw memory and swap some bytes around. C# doesn’t really allow the same access.  Through a little searching I found the BitConverter class which lets me accomplish the same task.  It doesn’t let me do it in-place, but it works well enough. 

The C# code snippet below will do the job of reading the main file header of the shapefile.  In future posts I’ll look at reading the actual map data out of the file and perhaps we’ll even display it.

// Copyright Microsoft Corp. All rights reserved.
// This code is provided AS-IS implies no warranties and confers no rights

using System;
using System.IO;

namespace AlsShapeReader
{
    // Shape type codes
    enum ShapeType
    {
        NullShape = 0,
        Point = 1,
        PolyLine = 3,
        Polygon = 5,
        MultiPoint = 8,
        PointZ = 11,
        PolyLineZ = 13,
        PolygonZ = 15,
        MultiPointZ = 18,
        PointM = 21,
        PolyLineM = 23,
        PolygonM = 25,
        MultiPointM = 28,
        MultiPatch = 31
    }

    // A utility class to help read bigendian integers.
    static class BinaryUtilities
    {
        // Given two bytes, this routine will swap them.
        static void SwapBytes(ref Byte first, ref Byte second)
        {
            Byte temp;
            temp = first;
            first = second;
            second = temp;
            return;
        }

        // Reverses the byte order in a 32bit integer
        public static Int32 ReverseBytes(Int32 value)
        {
            Int32 returnValue;
            byte[] bytes = BitConverter.GetBytes(value);

            SwapBytes(ref bytes[0], ref bytes[3]);
            SwapBytes(ref bytes[1], ref bytes[2]);

            returnValue = BitConverter.ToInt32(bytes, 0);

            return returnValue;
        }
    }

    // Class to hold the data from the main file header of a shape file
    class MainFileHeader
    {
        public Int32 FileCode { get; set; }
        public Int32 Unused1 { get; set; }
        public Int32 Unused2 { get; set; }
        public Int32 Unused3 { get; set; }
        public Int32 Unused4 { get; set; }
        public Int32 Unused5 { get; set; }
        public Int32 FileLength { get; set; }
        public Int32 Version { get; set; }
        public ShapeType Shape { get; set; }
        public Double XMin { get; set; }
        public Double YMin { get; set; }
        public Double XMax { get; set; }
        public Double YMax { get; set; }
        public Double ZMin { get; set; }
        public Double ZMax { get; set; }
        public Double MMin { get; set; }
        public Double MMax { get; set; }

        // Utility routine to populate the data from a Binary Reader
        public static MainFileHeader FromBinaryReader(BinaryReader reader)
        {
            MainFileHeader Header = new MainFileHeader();

            Header.FileCode = BinaryUtilities.ReverseBytes(reader.ReadInt32());
            Header.Unused1 = BinaryUtilities.ReverseBytes(reader.ReadInt32());
            Header.Unused2 = BinaryUtilities.ReverseBytes(reader.ReadInt32());
            Header.Unused3 = BinaryUtilities.ReverseBytes(reader.ReadInt32());
            Header.Unused4 = BinaryUtilities.ReverseBytes(reader.ReadInt32());
            Header.Unused5 = BinaryUtilities.ReverseBytes(reader.ReadInt32());
            Header.FileLength = BinaryUtilities.ReverseBytes(reader.ReadInt32());
            Header.Version = reader.ReadInt32();
            Header.Shape = (ShapeType)reader.ReadInt32();
            Header.XMin = reader.ReadDouble();
            Header.YMin = reader.ReadDouble();
            Header.XMax = reader.ReadDouble();
            Header.YMax = reader.ReadDouble();
            Header.ZMin = reader.ReadDouble();
            Header.ZMax = reader.ReadDouble();
            Header.MMin = reader.ReadDouble();
            Header.MMax = reader.ReadDouble();

            return Header;
        }
    }

    class Program
    {

        static void Main(string[] args)
        {
            BinaryReader reader;
            FileStream file;
            MainFileHeader header;

            String fileName = @"C:\Users\alanlu\Downloads\New Folder\statesp020.shp";
            file = File.Open(fileName, FileMode.Open);
            reader = new BinaryReader(file);
            header = MainFileHeader.FromBinaryReader(reader);
        }
    }
}