Episode #66

Processing Shape Files

21 minutes
Published on May 9, 2013

This video is only available to subscribers. Get access to this video and 572 others.

In this episode I cover how to parse ESRI Shapefiles and their DBF attribute counterparts, in order to get a list of vertices of US State boundaries. This episode covers interacting with a C library (shapelib). The data extracted will be useful in the next episode.

Episode Links

Once you download a collection of shape files, you'll have a few files to deal with. We're interested in the .dbf, .shx, and .shp files.

In order to see what the metadata about the shapes are, we'll first have to parse the DBF file.

Parsing DBF files

Make sure to read the DBF API documentation to understand how to read these files.

- (void)readShapeAttributes {
    NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
    NSString *dbfPath = [resourcePath stringByAppendingPathComponent:@"states.dbf"];

    const char * pszPath = [dbfPath cStringUsingEncoding:NSUTF8StringEncoding];
    DBFHandle dbf = DBFOpen(pszPath, "rb");

    int fieldCount = DBFGetFieldCount(dbf);
    for (int f=0; f<fieldCount; f++) {
        char fieldName[12];
        int width;
        int numDecimals;
        DBFFieldType type = DBFGetFieldInfo(dbf, f, fieldName, &width, &numDecimals);
        NSString *typeString = [self descriptionForFieldType:type];

        NSLog(@"Column %d:  %s (%@)", f, fieldName, typeString);
    }

    int recordCount = DBFGetRecordCount(dbf);
    for (int r=0; r<recordCount; r++) {
        int nameIndex = DBFGetFieldIndex(dbf, "STATE_NAME");
        const char * fieldName = DBFReadStringAttribute(dbf, r, nameIndex);
        NSLog(@"%s", fieldName);

        int seqIndex = DBFGetFieldIndex(dbf, "DRAWSEQ");
        int seq = DBFReadIntegerAttribute(dbf, r, seqIndex);
        NSLog(@"Seq: %d", seq);
    }

    DBFClose(dbf);
}

Next, we need to parse the shape files. To do this, we simply base the base filename to the SHP API and it will read the vertex file (.shp) as well as the index file (.shx).

Parsing Shape files


- (void)readShapeData {
    NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
    NSString *shpPath = [resourcePath stringByAppendingPathComponent:@"states"];

    const char * pszPath = [shpPath cStringUsingEncoding:NSUTF8StringEncoding];
    SHPHandle shp = SHPOpen(pszPath, "rb");

    SHPObject *shpObject = SHPReadObject(shp, 40);
    int numVertices = shpObject->nVertices;
    int numParts = shpObject->nParts;

    NSLog(@"Shape 40 has %d vertices across %d parts", numVertices, numParts);
    WARState *state = [[WARState alloc] initWithShapeObject:shpObject];
    NSLog(@"Created %@", state);
    NSLog(@"State has %d polygons", state.polygons.count);
    NSLog(@"First polygon has %d vertices", [[state.polygons[0] coordinates] count]);


    SHPClose(shp);
}

We're deferring most of the parsing to a new class WARState which will serve as a higher level container for this data we're working with.

Parsing individual states as a collection of polygons

Each state has a number of polygons (think of Hawaii having multiple islands). Each polygon has a list of vertices (or coordinates on a map) that we need to keep track of.

- (id)initWithShapeObject:(SHPObject *)shape {
    self = [super init];
    if (self) {
        int numParts = shape->nParts;
        int totalVertexCount = shape->nVertices;

        self.minLat = shape->dfYMin;
        self.maxLat = shape->dfYMax;
        self.minLong = shape->dfXMin;
        self.maxLong = shape->dfXMax;

        self.color = [[UIColor redColor] colorWithAlphaComponent:0.7];

        NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:numParts];
        for (int n=0; n<numParts; n++) {
            int startVertex = shape->panPartStart[n];
            int partVertexCount = (n == numParts - 1) ? totalVertexCount - startVertex : shape->panPartStart[n+1] - startVertex;
            WARPolygon *polygon = [[WARPolygon alloc] init];
            NSMutableArray *coordinates = [NSMutableArray arrayWithCapacity:partVertexCount];
            int endIndex = startVertex + partVertexCount;
            for (int pv=startVertex; pv<endIndex; pv++) {
                CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(shape->padfY[pv],
                                                                          shape->padfX[pv]);

                [coordinates addObject:[NSValue valueWithMKCoordinate:coord]];
                polygon.coordinates = coordinates;
            }
            [polygons addObject:polygon];
            self.polygons = polygons;
        }
    }
    return self;
}

Now that we have a collection of higher level Objective-C objects we can work with, we can do interesting things with it on the map. We're saving that for the next episode.