Class ShapefileGeometryReader

java.lang.Object
edu.cnu.mdi.mapping.shapefile.ShapefileGeometryReader
All Implemented Interfaces:
Closeable, AutoCloseable

public final class ShapefileGeometryReader extends Object implements Closeable
Zero-dependency reader for the binary geometry data in ESRI Shapefile (.shp) files.

File structure

A .shp file consists of:

  1. A 100-byte file header (mixed endianness — see below).
  2. A sequence of variable-length geometry records, each preceded by an 8-byte big-endian record header.

Endianness quirk

The ESRI specification uses big-endian integers in the file header (file code, file length) and in each record header (record number, content length), but little-endian integers and doubles everywhere else. This reader handles both transparently using ByteBuffer.order().

Supported shape types

Reader for the binary geometry data in ESRI Shapefile
Type valueNameUsed for
0Null ShapeReturned with empty geometry
1PointCity / populated-place data
3PolyLineRoad, river, and boundary data
5PolygonCountry boundary data
8MultiPointMulti-point city data

Unsupported types, including Z/M variants, cause an IOException.

Polygon ring handling

ESRI Polygons store all rings (outer shells and interior holes) in a flat Parts array. The winding order distinguishes shells (clockwise in the ESRI convention, which is counter-clockwise in geographic north-up orientation) from holes (opposite winding). This reader returns all rings without filtering holes so that the country renderer — which simply fills every ring — produces correct results for filled rendering. Callers that need topologically correct hole handling can use isClockwise(List) to classify rings.

Coordinate output

All returned coordinates are in the source file's native units. For Natural Earth data this is decimal degrees (WGS84). Callers must convert to radians and wrap longitude before passing to IMapProjection.

Usage


 try (ShapefileGeometryReader shp = new ShapefileGeometryReader(path)) {
     ShapefileGeometryReader.ShapeRecord record;
     while ((record = shp.nextRecord()) != null) {
         int type = record.shapeType();
         if (type == ShapefileGeometryReader.TYPE_POLYGON) {
             for (List<Point2D.Double> ring : record.rings()) {
                 // process ring
             }
         }
     }
 }
 

Thread safety

Instances are not thread-safe. Each calling thread should open its own reader.

  • Field Details

    • TYPE_NULL

      public static final int TYPE_NULL
      Shape type: no geometry present. Records of this type are skipped.
      See Also:
    • TYPE_POINT

      public static final int TYPE_POINT
      Shape type: a single (X, Y) point.
      See Also:
    • TYPE_POLYLINE

      public static final int TYPE_POLYLINE
      Shape type: an ordered set of vertices forming connected line segments.
      See Also:
    • TYPE_POLYGON

      public static final int TYPE_POLYGON
      Shape type: one or more rings forming a polygon.
      See Also:
    • TYPE_MULTIPOINT

      public static final int TYPE_MULTIPOINT
      Shape type: a set of points.
      See Also:
  • Constructor Details

    • ShapefileGeometryReader

      public ShapefileGeometryReader(Path path) throws IOException
      Opens the .shp file at the given path and validates the file header.
      Parameters:
      path - path to the .shp file; must not be null
      Throws:
      IOException - if the file cannot be opened, the file code is wrong, or the version is unsupported
  • Method Details

    • getFileShapeType

      public int getFileShapeType()
      Returns the shape type declared in the file header.

      All records in a well-formed shapefile have this shape type (or TYPE_NULL). Use the TYPE_* constants to identify the type.

      Returns:
      file-level shape type
    • nextRecord

      Reads and returns the next geometry record from the file, or null when the end of the file is reached.

      Null-shape records (TYPE_NULL) are returned with empty point and ring lists. Retaining the record is essential because shapefile geometry and DBF attributes are paired by record position.

      The returned ShapefileGeometryReader.ShapeRecord contains:

      Returns:
      the next record, or null at end of file
      Throws:
      IOException - if a read error occurs or the record contains an unrecognized shape type
    • readAllRecords

      public List<ShapefileGeometryReader.ShapeRecord> readAllRecords() throws IOException
      Reads all records from the file sequentially and returns them as an unmodifiable list.

      This is a convenience method that repeatedly calls nextRecord() until null is returned. The channel is not rewound before reading — callers that need a fresh read should close and reopen the reader.

      Returns:
      unmodifiable list of all geometry records, including null shapes
      Throws:
      IOException - if any read error occurs
    • close

      public void close() throws IOException
      Specified by:
      close in interface AutoCloseable
      Specified by:
      close in interface Closeable
      Throws:
      IOException
    • isClockwise

      public static boolean isClockwise(List<Point2D.Double> ring)
      Tests whether a polygon ring is oriented clockwise in the ESRI convention (which uses a screen-space y-axis pointing downward).

      Uses the shoelace formula: a positive signed area indicates clockwise orientation in ESRI coordinates (outer shells are clockwise; holes are counter-clockwise).

      Note that geographic coordinates have the y-axis pointing upward, so the convention is inverted relative to screen space. For Natural Earth data this distinction rarely matters because the renderer fills all rings regardless of winding order.

      Parameters:
      ring - list of (longitude, latitude) points forming a closed ring
      Returns:
      true if the ring is clockwise in ESRI screen convention