Programming with JViews Maps > Introducing the Main Classes > Readers and Writers > The Shapefile Reader and Writer

The class relationship for the shapefile reader and writer is shown in Figure 1.5.

map_shapefile.png

Figure 1.5 Shapefile Reader and Writer UML Diagram

These classes are based on the Shapefile Specifications listed in the ESRI (Environmental Systems Research Institute) document: ESRI Shapefile Technical Specifications - An ESRI White Paper - July 1998.

The Shapefile format is the exchange format for vector maps of the ESRI. This format supports polygons, arcs, lines, and points. Each Shapefile contains one single theme, meaning that all the objects in the file are of the same type (either line, point, polygon, or another type of object). In the Shapefile format, a theme is essentially described with four different files:

This format does not contain information concerning the coordinate system used to reference the position of the graphic objects. Objects in Shapefiles are often positioned within a geographic coordinate system (IlvGeographicCoordinateSystem), but this is far from being the rule.

The Shapefile format comprises the following classes:

The complete source code for an ESRI shapefile demonstration can be found at <installdir>/jviews-maps81/samples/shape/index.html

The Shape Datasource

The IlvShapeDataSource class is a specialized data source that reads ESRI shapefiles.

Reading Shapefiles

The easiest way of reading shapefiles is to use the dedicated data source:

// Create the data source
IlvShapeDataSource source = new IlvShapeDataSource(fileName);
// affect the manager
source.setManager(manager);
// start the data source.
try {
  source.start();
} catch (Exception e) {
  e.printStackTrace();
}

In this example, the data source is simply invoked and started. You have to use the setManager() method to specify the manager into which the graphic objects are inserted. Graphic objects are instances of IlvMapPolyline, IlvMapGraphicPath or IlvMapPoint, depending on the feature contained in the shapefile. Additionally, the data source creates an IlvMapLayer. You can specify a IlvMapStyle to change the rendering of the graphic objects in this layer.

Modifying Graphic Object Rendering

The following example shows how to modify the rendering of the graphic objects produced by this data source by changing the style of the IlvMapLayer of the data source.

// create the data source
IlvShapeDataSource source = new IlvShapeDataSource(fileName);
source.setManager(manager);
// Assuming that the geometry of the shape file are areas.
IlvGraphicPathStyle style = new IlvGraphicPathStyle();
source.getInsertionLayer().setStyle(style);
style.setPaint(Color.blue);
try {
  source.start();
} catch (Exception e) {
  e.printStackTrace();
}

If you are not sure of the type of objects contained in the shape file, you can use the setAttribute method on the layer style, for example:

    IlvMapLayer layer = source.getInsertionLayer();
    layer.getStyle().setAttribute(IlvPolylineStyle.FOREGROUND,Color.black);
      layer.getStyle().setAttribute(IlvPolylineStyle.BACKGROUND,new
    Color(1,1,0.8f));

Classes for Reading the Shape Format

The ilog.views.maps.format.shapefile package includes the following classes:

The IlvSHPReader Class

The geometries stored in Shapefiles are not necessarily 2-D objects. Each point that makes up a shape object can be associated with measurements, or with measurements and an elevation.

Measurements are stored in an attribute of type IlvAttributeArray, which itself is stored in the map feature attribute of index 0.

The following are the shape types that are associated with measurements:

Elevations are stored in an attribute of type IlvAttributeArray, which itself is stored in the map feature attribute of index 1.

The following are the shape types that are associated with measurements and elevations:

Since the JViews Maps package does not have a predefined geometry to represent shape objects of type MULTIPATCH, which are essentially used for 3-D rendering, these are ignored. It is possible, however, to modify this behavior by subtyping the class IlvShapeSHPReader. Since shape objects are read in protected methods, modifying the reader to include new geometries requires minimal effort.

The IlvDBFReader Class

This reader is used exclusively for reading a file of the .dbf format. It can be used to iterate over a file as follows:

 try {
       IlvDBFReader reader = new IlvDBFReader("myFile.dbf");
       IlvFeatureAttributeProperty attributes = reader.getNextRecord();
       while (attributes != null) {
         // Process attributes.
         ...
        attributes = reader.getNextRecord();
       }
    } catch (Exception e) {
       e.printStackTrace();
    }

If the reader has been created from a file and not from a URL, you can access map feature attributes directly by specifying their record number:

reader.readRecord(index);
The IlvShapeFileReader Class

This reader reads the .shp file storing geometries and the .dbf file storing attributes simultaneously, and merges the information into a single IlvMapFeature object.

It can be instantiated in one of three ways:

Classes for Writing the Shape Format

The ilog.views.maps.format.shapefile package contains the following classes for writing Shapefiles.

The class IlvSHPWriter is used to generate the geometry and index parts of a Shapefile (.shp and .shx files), while the IlvDBFWriter is used to write the attribute file (.dbf extension).

Like with feature iterator, writing map features to a Shapefile consists of writing repeatedly map features using the methods IlvSHPWriter.writeFeature() and IlvDBFWriter.writeAttributes(), then call the close() method of writers to flush the data and write the headers.

The IlvSHPWriter Class

The IlvSHPWriter class manages the writing of geometries to a Shapefile, with the creation of the Shapefile index that allows direct access to Shapefile records.

The following example shows how to use this class to write the contents of a feature iterator to a file foo.shp, creating the index file at the same time.

try {
   IlvSHPWriter shpwriter = new IlvSHPWriter("foo.shp",
                                            "foo.shx");
  // Loop on features.
  IlvMapFeature feature = iterator.getNextFeature();
  while (feature != null) {
    shpwriter.writeFeature(feature);
    feature = iterator.getNextFeature();
  }
  shpwriter.close();
} catch (IOException e) {
  // Error processing.
  e.printStackTrace();
}

Note
The Shapefile format defines a header that can be completed only once all data is written. For this reason, it is mandatory to call the close() method of the shape writer once all data is written, so that the header is updated.

The IlvDBFWriter and IlvDBFAttributeInfo Classes

The IlvDBFWriter and IlvDBFAttributeInfo classes manage the writing of DBase III+ files (.dbf files). These files contain records corresponding to attributes of geometries contained within a Shapefile.

As .dbf files need records with fixed-size fields, it is important to choose a field size that is large enough to contain all data of a field, as well as a size small enough not to waste data.

The goal of the class IlvDBFAttributeInfo is to complement IlvAttributeInfoProperty for the definition of record fields.

The following example shows how to write the contents of an iterator to a set of .shp, .shx and .dbf files:

try {
  // Create the SHP writer.
  IlvSHPWriter shpwriter = new IlvSHPWriter("foo.shp", "foo.shx");

  // Read the first feature.
  IlvMapFeature feature = iterator.getNextFeature();

  // Create the DBF Writer.
  IlvDBFAttributeInfo info =
      new IlvDBFAttributeInfo(feature.getAttributeInfo());

  IlvDBFWriter dbfwriter = new IlvDBFWriter(info,
                                            "foo.dbf");

  // Loop on features.
  while (feature != null) {
    shpwriter.writeFeature(feature);
    dbfwriter.writeAttributes(feature.getAttributes());
    feature = iterator.getNextFeature();
  }
  shpwriter.close();
  dbfwriter.close();
} catch (IOException e) {
  // Error processing.
  e.printStackTrace();
}

Once again, the writers must be closed to write the headers correctly.

Shapefile Load-On-Demand

The Maps package provides classes to perform load-on-demand on Shapefiles. This is achieved by the use of specific spatial index files. These files, usually having an .idx extension, store relations between tiles and object identifiers that belong to these tiles. A class and a tool example are provided to generate these spatial index files. A generic tile loader is also provided to minimize the amount of code needed to implement the load-on-demand mechanism using Shapefiles.

The load-on-demand mechanism involves two classes in addition to the shape reader and the dbf reader: the IlvShapeFileIndex class and the IlvShapeSpatialIndex class. A utility class is also provided to generate the spatial index for a given Shapefile: the IlvShapeFileTiler class.

The mechanism used to store and retrieve objects by tiles is illustrated in the following diagram:

mapsprg_introd6.gif

The Spatial Index file holds objects identifiers for each tile. Objects identifiers are their ordinal place in the IndexFile. Geometries are retrieved in the Shapefile using the IndexFile. In the following example, the tile [2, 1] (tiles indices begin at 0) contains identifiers 2, 5 and 9 referring to the geometries g2 g5 and g9.

The classes used to perform load-on-demand on the Shapefiles are the following:

The IlvShapeFileIndex Class

This class allows you to directly access geometries in a Shapefile. The spatial index and the Shapefile must correspond to the same theme:

// Open the index file.
IlvShapeFileIndex index = new IlvShapeFileIndex("example.shx");
// Open the corresponding Shapefile.
IlvSHPReader shape = new IlvSHPReader("example.shp");
// Retrieve the feature for each index.
int count = index.getRecordCount();
for(int i = 0; i < count; i++)
    IlvMapFeature f = shape.getFeatureAt(i);

The IlvShapeSpatialIndex Class

This class stores tile information: tile size and count, and identifiers of the objects belonging to each tile. To retrieve objects from a tile specified by its row and column, use the getIdArray() method:

// Open the spatial index file.
IlvShapeSpatialIndex spatialIndex =
    new IlvShapeSpatialIndex("example.shx");
// Loop on all columns and rows.
for(int c = 0; c < spatialindex.getColumnCount(); c++) {
    for(int r = 0; r < spatialindex.getRowCount(); r++) {
        // Retrieve the IDs of objects belonging to the tile at row `r' and 
        // column `c'.
        int[] ids = spatialindex.getIdArray(c, r);
        // Loop on these IDs and get the corresponding map feature.
        for(int i =0; i < ids.length; i++) {
            IlvMapFeature f = shape.getFeatureAt(i);
        }
    }
}

The IlvShapeFileTiler Class

This class is used to generate tiling information from a given Shapefile. To use this class you have to provide the Shapefile to tile, the SpatialIndexFile to write to, and either the tile size or the number of rows and columns.

IlvShapeFileTiler.CreateShapeSpatialIndex("example.shp", 
                                          "example.idx", 
                                                     5., 10.);              

The above code extract produces a SpatialIndexFile named example.idx with a tile size of width 5 and height 10.

IlvShapeFileTiler.CreateShapeSpatialIndex("example.shp", 
                                          "example.idx", 
                                          20, 30);

The above code extract produces a SpatialIndexFile of 600 tiles, 20 columns and 30 rows.

The IlvTiledShapeDataSource

The IlvTiledShapeDataSource is the tiled version of the IlvShapeDataSource. This means that it takes advantage of the tiling and load-on-demand mechanism. To read a tiled shapefile with such a data source, you can use the following code:

IlvTiledShapeDataSource source = new IlvTiledShapeDataSource(fileName);
source.setManager(getManager());
try {
  source.start();
} catch (Exception e) {
  e.printStackTrace();
}

The IlvShapeFileTileLoader Class

This class implements load-on-demand for tiled Shapefiles. When associated with an IlvTiledLayer, this class automatically handles tile loading if the Shapefile file name, the IndexFile file name, and the SpatialIndexFile file name are provided. An optional Dbase file name can also be provided to load object attributes.

IlvShapeFileTileLoader tileLoader = 
    new IlvShapeFileTileLoader("example.shp",
                               "example.dbf", // Or null if attributes loading 
                                             // is not wanted.
                               "example.shx",
                               "example.idx");
IlvTiledLayer tiledLayer = new IlvTiledLayer(new IlvRect(), null,
   IlvTileController.FREE);
tiledLayer.setTileLoader(tileLoader);