PyShp (The Python Shapefile Library)

repository·master·Indexed 22 days ago

https://github.com/geospatialpython/pyshp

A pure Python library for reading and writing ESRI Shapefiles, supporting .shp (geometry), .shx (index), and .dbf (attribute data) formats without requiring external C libraries. It provides Reader and Writer classes, supports GeoJSON conversion via __geo_interface__, and allows reading from local files, Zipfiles, and URLs.

Tokens
14.9K
Snippets
52
Records
68
Agent score
28%

What's inside pyshp

  1. Overview of PyShp

    master
    PyShp (The Python Shapefile Library) is a pure Python library designed to read and write ESRI Shapefiles. It provides support for the .shp (geometry), .shx (index), and .dbf (attribute data) file formats. Because it is written in pure Python, it is highly portable and does not require external C dependencies to handle standard GIS vector data.
  2. Understand PyShp's Unicode handling and padding

    master

    PyShp implements specific logic to handle the complexities of storing Unicode in DBF files, which traditionally expect ASCII.

    Key Behaviors:

    • Padding Removal: During decoding, PyShp removes padding bytes (null bytes or spaces). If decoding fails, it restores them one by one until it succeeds, issuing a warning if padding was required.
    • Truncation: When writing text fields, PyShp truncates strings to fit the DBF size or the 10-byte field name limit. It does this by truncating one code point at a time to avoid corrupting multi-byte characters. A warning is issued if truncation occurs.
    • Null Characters: PyShp warns if a null character is found in a decoded string or if a field name contains a null character before encoding.
    • Recommendation: To avoid most issues, use UTF-8 and avoid UTF-16 or UTF-32 encodings, as these can interact poorly with DBF padding and byte-boundary truncation.
  3. Balance geometry and records

    master

    A valid shapefile requires an equal number of shapes and records. If you add shapes and records in different orders or skip some, the file may become corrupt.

    To prevent this, use one of these two mechanisms:

    1. Auto-balancing: Set w.autoBalance = True (or 1). PyShp will automatically add null shapes or null records to keep the counts synchronized.
    2. Manual balancing: Call w.balance() at any time to synchronize the geometry and attribute sides.
    # Enable auto-balancing
    >>> w.autoBalance = 1
    >>> w.record("row", "three")
    >>> w.point(4, 4)
    
    # Manual balancing
    >>> w.autoBalance = 0
    >>> w.record("row", "five")
    >>> w.point(5, 5)
    >>> w.balance()
  4. Breaking changes in PyShp v3.0.0

    master

    Version 3.0.0 introduced several breaking changes for users migrating from older versions:

    • Python Support: Support for Python 2 and Python 3.8 has been dropped. Minimum supported version is Python 3.9.
    • Field Information: The field info tuple is now a Field namedtuple instead of a list.
    • Field Types: Field type codes are now FieldType enum members.
    • Bounding Boxes: bbox, mbox, and zbox attributes are now Namedtuple objects.
    • Writer Behavior: The Writer class no longer mutates Shape objects.
    • Shape Subclasses: New custom subclasses are provided for each shape type: Null, Multipatch, Point, Polyline, Multipoint, and Polygon (including M and Z variants). While Reader and Writer remain compatible with the base Shape class, these specific subclasses are now available.
    • Serialization: Shape subclasses can be created from and serialized to bytes streams according to the shapefile specification.
  5. Write large shapefiles using a streaming approach

    master

    The shapefile.Writer class uses a streaming approach. Geometries and records are written to disk immediately when shape() or record() is called. This allows for writing arbitrarily large files without consuming excessive memory.

    Important: You must call w.close() (or ensure the writer is garbage collected) to trigger the calculation and writing of the final header information to the beginning of the file.

    import shapefile
    
    w = shapefile.Writer('output_file')
    w.field('NAME', 'C')
    w.record('Example Name')
    w.shape([ [0,0], [1,1], [1,0] ])
    w.close()  # Essential to write the header
  6. Breaking changes in PyShp v2.0.0

    master

    Version 2.0.0 introduced major architectural changes that are incompatible with version 1.x:

    • Unicode Support: Full support for unicode text with custom encoding. Reader returns unicode and Writer accepts unicode.
    • Simplified API: The library is now a pure input-output library using Reader and Writer classes; the Editor class has been removed.
    • Streaming Writer: The Writer uses a streaming approach to minimize memory usage. You specify the filepath/destination and text encoding when creating the Writer, and files are written incrementally with each call to shape() and record().
    • Convenient Reading:
      • Reader supports the context manager pattern (using with).
      • Reader is iterable and supports len().
      • Reader supports the geo interface.
      • Record values can be accessed more conveniently as attributes.
    • Attribute Renaming: Reader attributes elevation and measure were renamed to zbox and mbox to clarify they represent min/max values.
  7. Efficiently reading large shapefiles with iterators

    master

    When working with very large shapefiles, avoid using records() or shapes() as they attempt to load the entire file into memory, which can cause a MemoryError. Instead, use iterator methods that process the file contents one at a time to keep memory usage minimal.

    Use the following methods for streaming data:

    • iterShapes(): Iterates through geometries.
    • iterRecords(): Iterates through attribute records.
    • iterShapeRecords(): Iterates through both geometry and records.
    • Iterating directly over the Reader object (e.g., for shapeRec in sf:) is equivalent to iterShapeRecords().
    sf = shapefile.Reader("path_to_large_file.zip")
    
    # Use iterators to avoid MemoryError
    for shapeRec in sf.iterShapeRecords():
        # process shape and record
        pass
  8. Write shapefiles to local files

    master

    To create a shapefile, instantiate a shapefile.Writer with a base file path. File extensions are optional; if provided, PyShp ignores them. You can specify a base name for all three component files (shp, dbf, shx) or specify individual names for specific file types. If a file type is not assigned a name, it will not be saved.

    >>> w = shapefile.Writer('tests/shapefiles/test/testfile')
    >>> w.field('field1', 'C')
    
    # To write only a DBF file:
    >>> w = shapefile.Writer(dbf='tests/shapefiles/test/onlydbf.dbf')
  9. Create a .prj file for projections

    master

    A .prj file stores the map projection (WKT string) and is required for GIS software to locate geometry correctly. The .prj file must have the same base name as your shapefile (e.g., myFile.prj for myFile.shp).

    You can create one manually by writing a Well-Known-Text (WKT) string to a file with the .prj extension.

    # Example: Creating a WGS 84 .prj file
    filename = 'myPoints'
    with open("{}.prj".format(filename), "w") as prj:
        wkt = 'GEOGCS["WGS 84",'
        wkt += 'DATUM["WGS_1984",'
        wkt += 'SPHEROID["WGS 84",6378137,298.257223563]]'
        wkt += ',PRIMEM["Greenwich",0],'n    wkt += 'UNIT["degree",0.0174532925199433]]'
        prj.write(wkt)
  10. Edit shapefiles by filtering and rewriting

    master

    To edit a shapefile (e.g., to remove fields or filter records), you must read the original file record-by-record and write the desired subset to a new file using shapefile.Writer.

    Workflow:

    1. Initialize a Writer.
    2. Define and write the new field schema using w.field().
    3. Iterate through the source Reader using iterShapeRecords() (optionally with the fields argument to limit data read).
    4. Write the filtered/modified records and shapes to the new file.
    5. Close the writer.
    w = shapefile.Writer('edited_output')
    r = shapefile.Reader('original_file')
    keep_fields = ['ID', 'VALUE']
    
    # Define new schema
    for field in r.fields[1:]:
        if field[0] in keep_fields:
            w.field(*field)
    
    # Write filtered data
    for shapeRec in r.iterShapeRecords(fields=keep_fields):
        w.record(*shapeRec.record)
        w.shape(shapeRec.shape)
    
    w.close()
  11. Use the Writer class as a context manager

    master

    While the Writer class automatically closes files and writes headers when garbage collected, it is recommended to call .close() manually to prevent data loss in case of crashes. Alternatively, use the with statement to ensure files are properly closed and headers are written automatically when exiting the block.

    >>> with shapefile.Writer("tests/shapefiles/test/contextwriter") as w:
    ...     w.field('field1', 'C')
    ...     pass
  12. Suppress PyShp logging and warnings

    master

    By default, PyShp provides logging information and warnings about non-critical issues. You can control this behavior using the shapefile.VERBOSE constant or by configuring the shapefile logger via the standard logging module.

    To suppress all output, set shapefile.VERBOSE = False or set the logging level for the shapefile namespace to ERROR.

    # Option 1: Use the module constant
    import shapefile
    shapefile.VERBOSE = False
    
    # Option 2: Use the logging module
    import logging
    logging.getLogger('shapefile').setLevel(logging.ERROR)