├── LICENSE.TXT ├── MANIFEST.in ├── shapefiles ├── test │ ├── dtype.dbf │ ├── dtype.shp │ ├── dtype.shx │ ├── line.shp │ ├── line.shx │ ├── point.shp │ ├── point.shx │ ├── MyPolyZ.shp │ ├── MyPolyZ.shx │ ├── NullTest.dbf │ ├── NullTest.shp │ ├── NullTest.shx │ ├── polygon.shp │ ├── polygon.shx │ ├── MyPolyZ.dbf │ ├── polygon.dbf │ ├── point.dbf │ └── line.dbf ├── blockgroups.dbf ├── blockgroups.sbn ├── blockgroups.sbx ├── blockgroups.shp └── blockgroups.shx ├── setup.py ├── .travis.yml ├── changelog.txt ├── README.md └── shapefile.py /LICENSE.TXT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/LICENSE.TXT -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md *.txt *.TXT 2 | recursive-include shapefiles *.dbf *.sbn *.sbx *.shp *.shx 3 | -------------------------------------------------------------------------------- /shapefiles/test/dtype.dbf: -------------------------------------------------------------------------------- 1 | uABOOLEANL T Y F N -------------------------------------------------------------------------------- /shapefiles/blockgroups.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/blockgroups.dbf -------------------------------------------------------------------------------- /shapefiles/blockgroups.sbn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/blockgroups.sbn -------------------------------------------------------------------------------- /shapefiles/blockgroups.sbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/blockgroups.sbx -------------------------------------------------------------------------------- /shapefiles/blockgroups.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/blockgroups.shp -------------------------------------------------------------------------------- /shapefiles/blockgroups.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/blockgroups.shx -------------------------------------------------------------------------------- /shapefiles/test/dtype.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/dtype.shp -------------------------------------------------------------------------------- /shapefiles/test/dtype.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/dtype.shx -------------------------------------------------------------------------------- /shapefiles/test/line.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/line.shp -------------------------------------------------------------------------------- /shapefiles/test/line.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/line.shx -------------------------------------------------------------------------------- /shapefiles/test/point.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/point.shp -------------------------------------------------------------------------------- /shapefiles/test/point.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/point.shx -------------------------------------------------------------------------------- /shapefiles/test/MyPolyZ.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/MyPolyZ.shp -------------------------------------------------------------------------------- /shapefiles/test/MyPolyZ.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/MyPolyZ.shx -------------------------------------------------------------------------------- /shapefiles/test/NullTest.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/NullTest.dbf -------------------------------------------------------------------------------- /shapefiles/test/NullTest.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/NullTest.shp -------------------------------------------------------------------------------- /shapefiles/test/NullTest.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/NullTest.shx -------------------------------------------------------------------------------- /shapefiles/test/polygon.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/polygon.shp -------------------------------------------------------------------------------- /shapefiles/test/polygon.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitoprincipe/pyshp/master/shapefiles/test/polygon.shx -------------------------------------------------------------------------------- /shapefiles/test/MyPolyZ.dbf: -------------------------------------------------------------------------------- 1 | q A3NAMEC2 PolyZTest -------------------------------------------------------------------------------- /shapefiles/test/polygon.dbf: -------------------------------------------------------------------------------- 1 | uaeFIRST_FLDC2SECOND_FLDC2 Appended Polygon -------------------------------------------------------------------------------- /shapefiles/test/point.dbf: -------------------------------------------------------------------------------- 1 | uaeFIRST_FLDC2SECOND_FLDC2 Appended Point -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='pyshp', 4 | version='1.2.11', 5 | description='Pure Python read/write support for ESRI Shapefile format', 6 | long_description=open('README.md').read(), 7 | author='Joel Lawhead', 8 | author_email='jlawhead@geospatialpython.com', 9 | url='https://github.com/GeospatialPython/pyshp', 10 | download_url='https://github.com/GeospatialPython/pyshp/archive/1.2.10.tar.gz', 11 | py_modules=['shapefile'], 12 | license='MIT', 13 | zip_safe=False, 14 | keywords='gis geospatial geographic shapefile shapefiles', 15 | classifiers=['Programming Language :: Python', 16 | 'Topic :: Scientific/Engineering :: GIS', 17 | 'Topic :: Software Development :: Libraries', 18 | 'Topic :: Software Development :: Libraries :: Python Modules']) 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - '2.7' 5 | - '3.3' 6 | - '3.4' 7 | - '3.5' 8 | 9 | install: 10 | - pip install . 11 | 12 | script: 13 | - python shapefile.py 14 | 15 | deploy: 16 | provider: pypi 17 | user: jlawhead 18 | password: 19 | secure: CI6igPEwYgPU0ZT4TfMsJ8D9HBfiGiEsPO01NHjx9kS1y6YdVedklnCGFn0J6qX7qHX/T995tS1OPU2izWVKkIIEOm9MbLzR4SSRqrHNOZAkXWClBkwIcoHEzQEnc5akRYBeisCqZtKn7w5EP1yquExHz+l2SKjwdS4HibA7wTA7g3tbX8oXIiFIANBy2vS18bmkxLCdV0lKdf8MU2TjavEghPL4vURyU31gMs5BaWHTsJH+2sLIzdBzYwkTeI+CKOmCr+jmLwHVMp0R43PfemSW5gK3BnLhrClfF4CU+Fu8Lypb6Glyo9kY6vsCf2d/5qzojx5j/1/rVeha7gJ35VodKnXZjgl+TwtUHiZL/MC/nqhPDx9ygtYZBjaVfgeWDAvqE0T+8KH1WhCwPDqFdjIuAmKqa5nJhWNueXNtmmDK9Bo0eUFgdHaThBbyPbRpqj5Pt9S82FIzOoWoxdy7Hv5D5xCmv4knjIcy9yDHj6KyTLDCHa4yH6aFJvYHj2Ml5nrLT/g08SyPl5kasZDnSIg1QI8A2GIJpjNbdqgCUwrpz+jZBGaAj/dq4UEfjNcjiaAQfBSdf2Suoz6z4oSLhXR9sC1+HgKG6OKHqt3hlq9hTUe8Tuj3DZ681eS00oXCPa5E5P4gDCoXUga9coZeGPOhbPBVIAQnxNRVaj6fqqU= 20 | on: 21 | tags: true 22 | distributions: sdist bdist_wheel 23 | repo: GeospatialPython/pyshp 24 | -------------------------------------------------------------------------------- /shapefiles/test/line.dbf: -------------------------------------------------------------------------------- 1 | uaQFIRST_FLDC(SECOND_FLDC( First Line Second Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line Appended Line -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | VERSION 1.2.11 2 | 3 | 2017-04-29 Karim Bahgat 4 | * Fixed bugs when reading and writing empty shapefiles. 5 | * Fixed bug when writing null geometry. 6 | * Fixed misc data type errors. 7 | * Fixed error when reading files with wrong record length. 8 | * Use max field precision when saving decimal numbers. 9 | * Improved shapetype detection. 10 | * Expanded docs on data types. 11 | * General doc additions and travis icon. 12 | 13 | VERSION 1.2.10 14 | 15 | 2016-09-24 Karim Bahgat 16 | * Bump version to fix pip install issue. 17 | 18 | VERSION 1.2.9 19 | 20 | 2016-09-22 Karim Bahgat 21 | * Revert back to fix #66. 22 | 23 | VERSION 1.2.8 24 | 25 | 2016-08-17 Joel Lawhead 26 | * Configured Travis-CI 27 | 28 | VERSION 1.2.5 29 | 30 | 2016-08-16 Joel Lawhead 31 | * Reader speed up through batch unpacking bytes 32 | * Merge README text into markdown file. Remove text version. 33 | * Fixed parsing of number of points for some shapes (MULTIPOINTM, MULTIPOINTZ) 34 | 35 | VERSON 1.2.3 36 | 37 | 2015-06-21 Joel Lawhead 38 | *shapefile.py (u) Bugfix for Python3 with Reader.iterShapeRecords() 39 | 40 | VERSION 1.2.2 41 | 42 | ### upcoming (2015/01/09 05:27 +00:00) 43 | - [#11](https://github.com/geospatialpython/pyshp/pull/11) Merge pull request #11 from 7mp/master (@7mp) 44 | - [#1](https://github.com/geospatialpython/pyshp/pull/1) Merge pull request #1 from rgbkrk/patch-1 (@rgbkrk) 45 | - [#13](https://github.com/geospatialpython/pyshp/pull/13) Merge pull request #13 from jzmiller1/patch-1 (@jzmiller1) 46 | - [#16](https://github.com/geospatialpython/pyshp/pull/16) Merge pull request #16 from riggsd/null-friendly (@riggsd) 47 | - [#17](https://github.com/geospatialpython/pyshp/pull/17) Merge pull request #17 from riggsd/no-asserts (@riggsd) 48 | - [#19](https://github.com/geospatialpython/pyshp/pull/19) Merge pull request #19 from razzius/master (@razzius) 49 | - [#20](https://github.com/geospatialpython/pyshp/pull/20) Merge pull request #20 from Brideau/patch-1 (@Brideau) 50 | - [12d69d4](https://github.com/GeospatialPython/pyshp/commit/12d69d47d8c90b445ea22bf5d9530b0c1c710de5) Updated to version 1.2.1 to match PyPI (@GeospatialPython) 51 | - [05b69dc](https://github.com/GeospatialPython/pyshp/commit/05b69dc6b3d58c0dc9a822f6c4b8d45cf8dc9d94) Updated to version 1.2.1 to match PyPI (@GeospatialPython) 52 | - [d2e9f1a](https://github.com/GeospatialPython/pyshp/commit/d2e9f1a41d02cf932484111f45c31781d1f7385a) Typo: recordsIter should be iterRecords (@Brideau) 53 | - [a965aff](https://github.com/GeospatialPython/pyshp/commit/a965aff230aa3f3b85016f7b627609c7e53a2cf9) Format README code sample (@razzius) 54 | - [66e1802](https://github.com/GeospatialPython/pyshp/commit/66e1802013fd3535baa505e15625afaa895ef819) Raise ShapefileException for runtime errors rather than using `assert()` 55 | - [d72723c](https://github.com/GeospatialPython/pyshp/commit/d72723c9e38db8e859b79d95a65c00af1c2ba8ba) Return None when parsing (illegal) NULL attribute values in numeric and date fields, like those produced by QGIS 56 | - [783e68c](https://github.com/GeospatialPython/pyshp/commit/783e68c75b8f20c7656ea470dbc5e9496a8ee0ac) Update link to "XBase File Format Description" (@jzmiller1) 57 | - [79cc409](https://github.com/GeospatialPython/pyshp/commit/79cc409362a24caf4a21923419490ee95d557dc3) Added `Reader.iterShapeRecords` to help work with larger files 58 | - [18c5521](https://github.com/GeospatialPython/pyshp/commit/18c5521b89cd1d7968dff8eb03c1ec37ab4307c5) URL Change (@rgbkrk) 59 | - [202143c](https://github.com/GeospatialPython/pyshp/commit/202143c823407ffea07b5400e77b9ded7169f696) README.md TOC Take 2 60 | - [2cca75c](https://github.com/GeospatialPython/pyshp/commit/2cca75cd09b27bb19a77ffeb68afc535e3c33802) README.md TOC 61 | - [8b5e994](https://github.com/GeospatialPython/pyshp/commit/8b5e994905fd4a70c0f7ce6d814346e6666b280c) README.md 62 | - [f31a3d7](https://github.com/GeospatialPython/pyshp/commit/f31a3d773dd22e65d3e38ad8b034f186a05b4c4d) Update README.txt (@GeospatialPython) 63 | 64 | VERSION 1.2.1 65 | 66 | 2014-05-11 Joel Lawhead 67 | *shapefile.py (u) fixed bug which failed to properly read some dbf fields in Python 3 68 | 69 | VERSION 1.2.0 70 | 71 | 2013-09-05 Joel Lawhead 72 | *README.txt add example/test for writing a 3D polygon 73 | 74 | VERSION 1.1.9 75 | 76 | 2013-07-27 Joel Lawhead 77 | *shapefile.py (Writer.__shpRecords) fixed inconsistency between Reader and Writer 78 | when referencing "z" and "m" values. This bug caused errors only when editing 79 | 3D shapefiles. 80 | 81 | VERSION 1.1.8 82 | 83 | 2013-07-02 Joel Lawhead 84 | *shapefile.py (Writer.poly()) fixed a bug that resulted in incorrect part indexes 85 | *README.txt updated several errors in the documentation. 86 | 87 | 2013-06-25 Joel Lawhead 88 | *shapefile.py (Reader.shapes(),Reader.iterShapes()) Updated to verify the file length by 89 | seeking to the end. A user reported shapefiles in the wild which had incorrect .shp file 90 | lengths reported in the header which crashed when reading or iterating shapes. Most 91 | insist on using the .shx file but there's no real reason to do so. 92 | 93 | VERSION 1.1.7 94 | 95 | 2013-06-22 Joel Lawhead 96 | 97 | *shapefile.py (_Shape.__geo_interface__) Added Python __geo_interface__ convention 98 | to export shapefiles as GeoJSON. 99 | 100 | *shapefile.py (Reader.__init__) Used is_string() method to detect filenames passed 101 | as unicode strings. 102 | 103 | *shapefile.py (Reader.iterShapes) Added iterShapes() method to iterate through 104 | geometry records for parsing large files efficiently. 105 | 106 | *shapefile.py (Reader.iterRecords) Added iterRecords() method to iterate through 107 | dbf records efficiently in large files. 108 | 109 | *shapefile.py (Reader.shape) Modified shape() method to use iterShapes() if shx 110 | file is not available. 111 | 112 | *shapefile.py (main) Added __version__ attribute. 113 | 114 | *shapefile.py (Writer.record) Fixed bug which prevents writing the number 0 to 115 | dbf fields. 116 | 117 | *shapefile.py (Reader.__shape) Updated to calculate and seek the start of the next record. The 118 | shapefile spec does not require the content of a geometry record to be as long as the content 119 | length defined in the header. The result is you can delete features without modifying the 120 | record header allowing for empty space in records. 121 | 122 | *shapefile.py (Writer.poly) Added enforcement of closed polygons 123 | 124 | *shapefile.py (Writer.save) Added unique file name generator to use if no file names are passed 125 | to a writer instance when saving (ex. w.save()). The unique file name is returned as a string. 126 | 127 | *README.txt (main) Added tests for iterShapes(), iterRecords(), __geo_interface__() 128 | 129 | *README.txt (main) Updated "bbox" property documentation to match Esri specification. 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyShp 2 | 3 | The Python Shapefile Library (pyshp) reads and writes ESRI Shapefiles in pure Python. 4 | 5 | ![pyshp logo](http://4.bp.blogspot.com/_SBi37QEsCvg/TPQuOhlHQxI/AAAAAAAAAE0/QjFlWfMx0tQ/S350/GSP_Logo.png "PyShp") 6 | 7 | [![Build Status](https://travis-ci.org/GeospatialPython/pyshp.svg?branch=master)](https://travis-ci.org/GeospatialPython/pyshp) 8 | 9 | ## Contents 10 | 11 | [Overview](#overview) 12 | 13 | [Examples](#examples) 14 | - [Reading Shapefiles](#reading-shapefiles) 15 | - [Reading Shapefiles from File-Like Objects](#reading-shapefiles-from-file-like-objects) 16 | - [Reading Shapefile Meta-Data](#reading-shapefile-meta-data) 17 | - [Reading Geometry](#reading-geometry) 18 | - [Reading Records](#reading-records) 19 | - [Reading Geometry and Records Simultaneously](#reading-geometry-and-records-simultaneously) 20 | - [Writing Shapefiles](#writing-shapefiles) 21 | - [Setting the Shape Type](#setting-the-shape-type) 22 | - [Geometry and Record Balancing](#geometry-and-record-balancing) 23 | - [Adding Geometry](#adding-geometry) 24 | - [Adding Records](#adding-records) 25 | - [File Names](#file-names) 26 | - [Saving to File-Like Objects](#saving-to-file-like-objects) 27 | - [Working with Large Shapefiles](#working-with-large-shapefiles) 28 | - [Python Geo Interface](#python-geo-interface) 29 | - [Testing](#testing) 30 | 31 | # Overview 32 | 33 | The Python Shapefile Library (pyshp) provides read and write support for the 34 | Esri Shapefile format. The Shapefile format is a popular Geographic 35 | Information System vector data format created by Esri. For more information 36 | about this format please read the well-written "ESRI Shapefile Technical 37 | Description - July 1998" located at [http://www.esri.com/library/whitepapers/p 38 | dfs/shapefile.pdf](http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf) 39 | . The Esri document describes the shp and shx file formats. However a third 40 | file format called dbf is also required. This format is documented on the web 41 | as the "XBase File Format Description" and is a simple file-based database 42 | format created in the 1960's. For more on this specification see: [http://www.clicketyclick.dk/databases/xbase/format/index.html](http://www.clicketyclick.d 43 | k/databases/xbase/format/index.html) 44 | 45 | Both the Esri and XBase file-formats are very simple in design and memory 46 | efficient which is part of the reason the shapefile format remains popular 47 | despite the numerous ways to store and exchange GIS data available today. 48 | 49 | Pyshp is compatible with Python 2.7-3.x. 50 | 51 | This document provides examples for using pyshp to read and write shapefiles. However 52 | many more examples are continually added to the pyshp wiki on GitHub, the blog [http://GeospatialPython.com](http://GeospatialPython.com), 53 | and by searching for pyshp on [https://gis.stackexchange.com](https://gis.stackexchange.com). 54 | 55 | Currently the sample census blockgroup shapefile referenced in the examples is available on the GitHub project site at 56 | [https://github.com/GeospatialPython/pyshp](https://github.com/GeospatialPython/pyshp). These 57 | examples are straight-forward and you can also easily run them against your 58 | own shapefiles with minimal modification. 59 | 60 | Important: If you are new to GIS you should read about map projections. 61 | Please visit: [https://github.com/GeospatialPython/pyshp/wiki/Map-Projections](https://github.com/GeospatialPython/pyshp/wiki/Map-Projections) 62 | 63 | I sincerely hope this library eliminates the mundane distraction of simply 64 | reading and writing data, and allows you to focus on the challenging and FUN 65 | part of your geospatial project. 66 | 67 | # Examples 68 | 69 | Before doing anything you must import the library. 70 | 71 | 72 | >>> import shapefile 73 | 74 | The examples below will use a shapefile created from the U.S. Census Bureau 75 | Blockgroups data set near San Francisco, CA and available in the git 76 | repository of the pyshp GitHub site. 77 | 78 | ## Reading Shapefiles 79 | 80 | To read a shapefile create a new "Reader" object and pass it the name of an 81 | existing shapefile. The shapefile format is actually a collection of three 82 | files. You specify the base filename of the shapefile or the complete filename 83 | of any of the shapefile component files. 84 | 85 | 86 | >>> sf = shapefile.Reader("shapefiles/blockgroups") 87 | 88 | OR 89 | 90 | 91 | >>> sf = shapefile.Reader("shapefiles/blockgroups.shp") 92 | 93 | OR 94 | 95 | 96 | >>> sf = shapefile.Reader("shapefiles/blockgroups.dbf") 97 | 98 | OR any of the other 5+ formats which are potentially part of a shapefile. The 99 | library does not care about file extensions. 100 | 101 | ### Reading Shapefiles from File-Like Objects 102 | 103 | You can also load shapefiles from any Python file-like object using keyword 104 | arguments to specify any of the three files. This feature is very powerful and 105 | allows you to load shapefiles from a url, from a zip file, serialized object, 106 | or in some cases a database. 107 | 108 | 109 | >>> myshp = open("shapefiles/blockgroups.shp", "rb") 110 | >>> mydbf = open("shapefiles/blockgroups.dbf", "rb") 111 | >>> r = shapefile.Reader(shp=myshp, dbf=mydbf) 112 | 113 | Notice in the examples above the shx file is never used. The shx file is a 114 | very simple fixed-record index for the variable length records in the shp 115 | file. This file is optional for reading. If it's available pyshp will use the 116 | shx file to access shape records a little faster but will do just fine without 117 | it. 118 | 119 | ### Reading Shapefile Meta-Data 120 | 121 | Shapefiles have a number of attributes for inspecting the file contents. 122 | A shapefile is a container for a specific type of geometry, and this can be checked using the 123 | shapeType attribute. 124 | 125 | 126 | >>> sf.shapeType 127 | 5 128 | 129 | Shape types are represented by numbers between 0 and 31 as defined by the 130 | shapefile specification and listed below. It is important to note that numbering system has 131 | several reserved numbers which have not been used yet therefore the numbers of 132 | the existing shape types are not sequential: 133 | 134 | - NULL = 0 135 | - POINT = 1 136 | - POLYLINE = 3 137 | - POLYGON = 5 138 | - MULTIPOINT = 8 139 | - POINTZ = 11 140 | - POLYLINEZ = 13 141 | - POLYGONZ = 15 142 | - MULTIPOINTZ = 18 143 | - POINTM = 21 144 | - POLYLINEM = 23 145 | - POLYGONM = 25 146 | - MULTIPOINTM = 28 147 | - MULTIPATCH = 31 148 | 149 | Based on this we can see that our blockgroups shapefile contains 150 | Polygon type shapes. The shape types are also defined as constants in 151 | the shapefile module, so that we can compare types more intuitively: 152 | 153 | 154 | >>> sf.shapeType == shapefile.POLYGON 155 | True 156 | 157 | Other pieces of meta-data that we can check includes the number of features, 158 | or the bounding box area the shapefile covers: 159 | 160 | 161 | >>> len(sf) 162 | 663 163 | >>> sf.bbox 164 | [-122.515048, 37.652916, -122.327622, 37.863433] 165 | 166 | ### Reading Geometry 167 | 168 | A shapefile's geometry is the collection of points or shapes made from 169 | vertices and implied arcs representing physical locations. All types of 170 | shapefiles just store points. The metadata about the points determine how they 171 | are handled by software. 172 | 173 | You can get the a list of the shapefile's geometry by calling the shapes() 174 | method. 175 | 176 | 177 | >>> shapes = sf.shapes() 178 | 179 | The shapes method returns a list of Shape objects describing the geometry of 180 | each shape record. 181 | 182 | 183 | >>> len(shapes) 184 | 663 185 | 186 | Each shape record contains the following attributes: 187 | 188 | 189 | >>> for name in dir(shapes[3]): 190 | ... if not name.startswith('__'): 191 | ... name 192 | 'bbox' 193 | 'parts' 194 | 'points' 195 | 'shapeType' 196 | 197 | * shapeType: an integer representing the type of shape as defined by the 198 | shapefile specification. 199 | 200 | 201 | >>> shapes[3].shapeType 202 | 5 203 | 204 | * bbox: If the shape type contains multiple points this tuple describes the 205 | lower left (x,y) coordinate and upper right corner coordinate creating a 206 | complete box around the points. If the shapeType is a 207 | Null (shapeType == 0) then an AttributeError is raised. 208 | 209 | 210 | >>> # Get the bounding box of the 4th shape. 211 | >>> # Round coordinates to 3 decimal places 212 | >>> bbox = shapes[3].bbox 213 | >>> ['%.3f' % coord for coord in bbox] 214 | ['-122.486', '37.787', '-122.446', '37.811'] 215 | 216 | * parts: Parts simply group collections of points into shapes. If the shape 217 | record has multiple parts this attribute contains the index of the first 218 | point of each part. If there is only one part then a list containing 0 is 219 | returned. 220 | 221 | 222 | >>> shapes[3].parts 223 | [0] 224 | 225 | * points: The points attribute contains a list of tuples containing an 226 | (x,y) coordinate for each point in the shape. 227 | 228 | 229 | >>> len(shapes[3].points) 230 | 173 231 | >>> # Get the 8th point of the fourth shape 232 | >>> # Truncate coordinates to 3 decimal places 233 | >>> shape = shapes[3].points[7] 234 | >>> ['%.3f' % coord for coord in shape] 235 | ['-122.471', '37.787'] 236 | 237 | To read a single shape by calling its index use the shape() method. The index 238 | is the shape's count from 0. So to read the 8th shape record you would use its 239 | index which is 7. 240 | 241 | 242 | >>> s = sf.shape(7) 243 | 244 | >>> # Read the bbox of the 8th shape to verify 245 | >>> # Round coordinates to 3 decimal places 246 | >>> ['%.3f' % coord for coord in s.bbox] 247 | ['-122.450', '37.801', '-122.442', '37.808'] 248 | 249 | ### Reading Records 250 | 251 | A record in a shapefile contains the attributes for each shape in the 252 | collection of geometry. Records are stored in the dbf file. The link between 253 | geometry and attributes is the foundation of all geographic information systems. 254 | This critical link is implied by the order of shapes and corresponding records 255 | in the shp geometry file and the dbf attribute file. 256 | 257 | The field names of a shapefile are available as soon as you read a shapefile. 258 | You can call the "fields" attribute of the shapefile as a Python list. Each 259 | field is a Python list with the following information: 260 | 261 | * Field name: the name describing the data at this column index. 262 | * Field type: the type of data at this column index. Types can be: Character, 263 | Numbers, Longs, Dates, or Memo. The "Memo" type has no meaning within a 264 | GIS and is part of the xbase spec instead. 265 | * Field length: the length of the data found at this column index. Older GIS 266 | software may truncate this length to 8 or 11 characters for "Character" 267 | fields. 268 | * Decimal length: the number of decimal places found in "Number" fields. 269 | 270 | To see the fields for the Reader object above (sf) call the "fields" 271 | attribute: 272 | 273 | 274 | >>> fields = sf.fields 275 | 276 | >>> assert fields == [("DeletionFlag", "C", 1, 0), ["AREA", "N", 18, 5], 277 | ... ["BKG_KEY", "C", 12, 0], ["POP1990", "N", 9, 0], ["POP90_SQMI", "N", 10, 1], 278 | ... ["HOUSEHOLDS", "N", 9, 0], 279 | ... ["MALES", "N", 9, 0], ["FEMALES", "N", 9, 0], ["WHITE", "N", 9, 0], 280 | ... ["BLACK", "N", 8, 0], ["AMERI_ES", "N", 7, 0], ["ASIAN_PI", "N", 8, 0], 281 | ... ["OTHER", "N", 8, 0], ["HISPANIC", "N", 8, 0], ["AGE_UNDER5", "N", 8, 0], 282 | ... ["AGE_5_17", "N", 8, 0], ["AGE_18_29", "N", 8, 0], ["AGE_30_49", "N", 8, 0], 283 | ... ["AGE_50_64", "N", 8, 0], ["AGE_65_UP", "N", 8, 0], 284 | ... ["NEVERMARRY", "N", 8, 0], ["MARRIED", "N", 9, 0], ["SEPARATED", "N", 7, 0], 285 | ... ["WIDOWED", "N", 8, 0], ["DIVORCED", "N", 8, 0], ["HSEHLD_1_M", "N", 8, 0], 286 | ... ["HSEHLD_1_F", "N", 8, 0], ["MARHH_CHD", "N", 8, 0], 287 | ... ["MARHH_NO_C", "N", 8, 0], ["MHH_CHILD", "N", 7, 0], 288 | ... ["FHH_CHILD", "N", 7, 0], ["HSE_UNITS", "N", 9, 0], ["VACANT", "N", 7, 0], 289 | ... ["OWNER_OCC", "N", 8, 0], ["RENTER_OCC", "N", 8, 0], 290 | ... ["MEDIAN_VAL", "N", 7, 0], ["MEDIANRENT", "N", 4, 0], 291 | ... ["UNITS_1DET", "N", 8, 0], ["UNITS_1ATT", "N", 7, 0], ["UNITS2", "N", 7, 0], 292 | ... ["UNITS3_9", "N", 8, 0], ["UNITS10_49", "N", 8, 0], 293 | ... ["UNITS50_UP", "N", 8, 0], ["MOBILEHOME", "N", 7, 0]] 294 | 295 | You can get a list of the shapefile's records by calling the records() method: 296 | 297 | 298 | >>> records = sf.records() 299 | 300 | >>> len(records) 301 | 663 302 | 303 | Each record is a list containing an attribute corresponding to each field in 304 | the field list. 305 | 306 | For example in the 4th record of the blockgroups shapefile the 2nd and 3rd 307 | fields are the blockgroup id and the 1990 population count of that San 308 | Francisco blockgroup: 309 | 310 | 311 | >>> records[3][1:3] 312 | ['060750601001', 4715] 313 | 314 | To read a single record call the record() method with the record's index: 315 | 316 | 317 | >>> rec = sf.record(3) 318 | 319 | >>> rec[1:3] 320 | ['060750601001', 4715] 321 | 322 | ### Reading Geometry and Records Simultaneously 323 | 324 | You may want to examine both the geometry and the attributes for a record at 325 | the same time. The shapeRecord() and shapeRecords() method let you do just 326 | that. 327 | 328 | Calling the shapeRecords() method will return the geometry and attributes for 329 | all shapes as a list of ShapeRecord objects. Each ShapeRecord instance has a 330 | "shape" and "record" attribute. The shape attribute is a ShapeRecord object as 331 | discussed in the first section "Reading Geometry". The record attribute is a 332 | list of field values as demonstrated in the "Reading Records" section. 333 | 334 | 335 | >>> shapeRecs = sf.shapeRecords() 336 | 337 | Let's read the blockgroup key and the population for the 4th blockgroup: 338 | 339 | 340 | >>> shapeRecs[3].record[1:3] 341 | ['060750601001', 4715] 342 | 343 | Now let's read the first two points for that same record: 344 | 345 | 346 | >>> points = shapeRecs[3].shape.points[0:2] 347 | 348 | >>> len(points) 349 | 2 350 | 351 | The shapeRecord() method reads a single shape/record pair at the specified index. 352 | To get the 4th shape record from the blockgroups shapefile use the third index: 353 | 354 | 355 | >>> shapeRec = sf.shapeRecord(3) 356 | 357 | The blockgroup key and population count: 358 | 359 | 360 | >>> shapeRec.record[1:3] 361 | ['060750601001', 4715] 362 | 363 | >>> points = shapeRec.shape.points[0:2] 364 | 365 | >>> len(points) 366 | 2 367 | 368 | 369 | ## Writing Shapefiles 370 | 371 | PyShp tries to be as flexible as possible when writing shapefiles while 372 | maintaining some degree of automatic validation to make sure you don't 373 | accidentally write an invalid file. 374 | 375 | PyShp can write just one of the component files such as the shp or dbf file 376 | without writing the others. So in addition to being a complete shapefile 377 | library, it can also be used as a basic dbf (xbase) library. Dbf files are a 378 | common database format which are often useful as a standalone simple database 379 | format. And even shp files occasionally have uses as a standalone format. Some 380 | web-based GIS systems use an user-uploaded shp file to specify an area of 381 | interest. Many precision agriculture chemical field sprayers also use the shp 382 | format as a control file for the sprayer system (usually in combination with 383 | custom database file formats). 384 | 385 | To create a shapefile you add geometry and/or attributes using methods in the 386 | Writer class until you are ready to save the file. 387 | 388 | Create an instance of the Writer class to begin creating a shapefile: 389 | 390 | 391 | >>> w = shapefile.Writer() 392 | 393 | ### Setting the Shape Type 394 | 395 | The shape type defines the type of geometry contained in the shapefile. All of 396 | the shapes must match the shape type setting. 397 | 398 | There are three ways to set the shape type: 399 | * Set it when creating the class instance. 400 | * Set it by assigning a value to an existing class instance. 401 | * Set it automatically to the type of the first non-null shape by saving the shapefile. 402 | 403 | To manually set the shape type for a Writer object when creating the Writer: 404 | 405 | 406 | >>> w = shapefile.Writer(shapeType=3) 407 | 408 | >>> w.shapeType 409 | 3 410 | 411 | OR you can set it after the Writer is created: 412 | 413 | 414 | >>> w.shapeType = 1 415 | 416 | >>> w.shapeType 417 | 1 418 | 419 | ### Geometry and Record Balancing 420 | 421 | Because every shape must have a corresponding record it is critical that the 422 | number of records equals the number of shapes to create a valid shapefile. You 423 | must take care to add records and shapes in the same order so that the record 424 | data lines up with the geometry data. For example: 425 | 426 | 427 | >>> w = shapefile.Writer(shapeType=shapefile.POINT) 428 | >>> w.field("field1", "C") 429 | >>> w.field("field2", "C") 430 | 431 | >>> w.record("row", "one") 432 | >>> w.point(1, 1) 433 | 434 | >>> w.record("row", "two") 435 | >>> w.point(2, 2) 436 | 437 | To help prevent accidental misalignment pyshp has an "auto balance" feature to 438 | make sure when you add either a shape or a record the two sides of the 439 | equation line up. This way if you forget to update an entry the 440 | shapefile will still be valid and handled correctly by most shapefile 441 | software. Autobalancing is NOT turned on by default. To activate it set 442 | the attribute autoBalance to 1 or True: 443 | 444 | 445 | >>> w.autoBalance = 1 446 | >>> w.record("row", "three") 447 | >>> w.record("row", "four") 448 | >>> w.point(4, 4) 449 | 450 | >>> w.recNum == w.shpNum 451 | True 452 | 453 | You also have the option of manually calling the balance() method at any time 454 | to ensure the other side is up to date. When balancing is used 455 | null shapes are created on the geometry side or records 456 | with a value of "NULL" for each field is created on the attribute side. 457 | This gives you flexibility in how you build the shapefile. 458 | You can create all of the shapes and then create all of the records or vice versa. 459 | 460 | 461 | >>> w.autoBalance = 0 462 | >>> w.record("row", "five") 463 | >>> w.record("row", "six") 464 | >>> w.record("row", "seven") 465 | >>> w.point(5, 5) 466 | >>> w.point(6, 6) 467 | >>> w.balance() 468 | 469 | >>> w.recNum == w.shpNum 470 | True 471 | 472 | If you do not use the autobalance or balance method and forget to manually 473 | balance the geometry and attributes the shapefile will be viewed as corrupt by 474 | most shapefile software. 475 | 476 | 477 | ### Adding Geometry 478 | 479 | Geometry is added using one of several convenience methods. The "null" method is used 480 | for null shapes, "point" is used for point shapes, "line" for lines, and 481 | "poly" is used for polygons and everything else. 482 | 483 | **Adding a Point shape** 484 | 485 | Point shapes are added using the "point" method. A point is specified by an x, 486 | y value. An optional z (elevation) and m (measure) value can be set if the shapeType 487 | is PointZ or PointM. 488 | 489 | 490 | >>> w = shapefile.Writer() 491 | >>> w.field('name', 'C') 492 | 493 | >>> w.point(122, 37) 494 | >>> w.record('point1') 495 | 496 | >>> w.save('shapefiles/test/point') 497 | 498 | **Adding a Polygon shape** 499 | 500 | Shapefile polygons must have at 501 | least 4 points and the last point must be the same as the first. PyShp 502 | automatically enforces closed polygons. 503 | 504 | 505 | >>> w = shapefile.Writer() 506 | >>> w.field('name', 'C') 507 | 508 | >>> w.poly(parts=[[[122,37,4,9], [117,36,3,4]], [[115,32,8,8], 509 | ... [118,20,6,4], [113,24]]]) 510 | >>> w.record('polygon1') 511 | 512 | >>> w.save('shapefiles/test/polygon') 513 | 514 | **Adding a Line shape** 515 | 516 | A line must have at least two points. 517 | Because of the similarities between polygon and line types it is possible to create 518 | a line shape using either the "line" or "poly" method. 519 | 520 | 521 | >>> w = shapefile.Writer() 522 | >>> w.field('name', 'C') 523 | 524 | >>> w.line(parts=[[[1,5],[5,5],[5,1],[3,3],[1,1]]]) 525 | >>> w.poly(parts=[[[1,3],[5,3]]], shapeType=shapefile.POLYLINE) 526 | 527 | >>> w.record('line1') 528 | >>> w.record('line2') 529 | 530 | >>> w.save('shapefiles/test/line') 531 | 532 | **Adding a Null shape** 533 | 534 | Because Null shape types (shape type 0) have no geometry the "null" method is 535 | called without any arguments. This type of shapefile is rarely used but it is valid. 536 | 537 | 538 | >>> w = shapefile.Writer() 539 | >>> w.field('name', 'C') 540 | 541 | >>> w.null() 542 | >>> w.record('nullgeom') 543 | 544 | >>> w.save('shapefiles/test/null') 545 | 546 | **Adding from an existing Shape object** 547 | 548 | Finally, geometry can be added by passing an existing "Shape" object to the "shape" method. 549 | This can be particularly useful for copying from one file to another: 550 | 551 | 552 | >>> r = shapefile.Reader('shapefiles/test/polygon') 553 | 554 | >>> w = shapefile.Writer() 555 | >>> w.fields = r.fields[1:] # skip first deletion field 556 | 557 | >>> for shaperec in r.iterShapeRecords(): 558 | ... w.record(*shaperec.record) 559 | ... w.shape(shaperec.shape) 560 | 561 | >>> w.save('shapefiles/test/copy') 562 | 563 | 564 | ### Adding Records 565 | 566 | Adding record attributes involves two steps. Step 1 is to create fields to contain 567 | attribute values and step 2 is to populate the fields with values for each 568 | shape record. 569 | 570 | There are several different field types, all of which support storing None values as NULL. 571 | 572 | Text fields are created using the 'C' type, and the third 'size' argument can be customized to the expected 573 | length of text values to save space: 574 | 575 | 576 | >>> w = shapefile.Writer() 577 | >>> w.field('TEXT', 'C') 578 | >>> w.field('SHORT_TEXT', 'C', size=5) 579 | >>> w.field('LONG_TEXT', 'C', size=250) 580 | >>> w.null() 581 | >>> w.record('Hello', 'World', 'World'*50) 582 | >>> w.save('shapefiles/test/dtype') 583 | 584 | >>> r = shapefile.Reader('shapefiles/test/dtype') 585 | >>> assert r.record(0) == ['Hello', 'World', 'World'*50] 586 | 587 | Date fields are created using the 'D' type, and can be created using either 588 | date objects, lists, or a YYYYMMDD formatted string. 589 | Field length or decimal have no impact on this type: 590 | 591 | 592 | >>> from datetime import date 593 | >>> w = shapefile.Writer() 594 | >>> w.field('DATE', 'D') 595 | >>> w.null() 596 | >>> w.null() 597 | >>> w.null() 598 | >>> w.null() 599 | >>> w.record(date(1998,1,30)) 600 | >>> w.record([1998,1,30]) 601 | >>> w.record('19980130') 602 | >>> w.record(None) 603 | >>> w.save('shapefiles/test/dtype') 604 | 605 | >>> r = shapefile.Reader('shapefiles/test/dtype') 606 | >>> assert r.record(0) == [date(1998,1,30)] 607 | >>> assert r.record(1) == [date(1998,1,30)] 608 | >>> assert r.record(2) == [date(1998,1,30)] 609 | >>> assert r.record(3) == [None] 610 | 611 | Numeric fields are created using the 'N' type (or the 'F' type, which is exactly the same). 612 | By default the fourth decimal argument is set to zero, essentially creating an integer field. 613 | To store floats you must set the decimal argument to the precision of your choice. 614 | To store very large numbers you must increase the field length size to the total number of digits 615 | (including comma and minus). 616 | 617 | 618 | >>> w = shapefile.Writer() 619 | >>> w.field('INT', 'N') 620 | >>> w.field('LOWPREC', 'N', decimal=2) 621 | >>> w.field('MEDPREC', 'N', decimal=10) 622 | >>> w.field('HIGHPREC', 'N', decimal=30) 623 | >>> w.field('FTYPE', 'F', decimal=10) 624 | >>> w.field('LARGENR', 'N', 101) 625 | >>> nr = 1.3217328 626 | >>> w.null() 627 | >>> w.null() 628 | >>> w.record(INT=int(nr), LOWPREC=nr, MEDPREC=nr, HIGHPREC=-3.2302e-25, FTYPE=nr, LARGENR=int(nr)*10**100) 629 | >>> w.record(None, None, None, None, None, None) 630 | >>> w.save('shapefiles/test/dtype') 631 | 632 | >>> r = shapefile.Reader('shapefiles/test/dtype') 633 | >>> assert r.record(0) == [1, 1.32, 1.3217328, -3.2302e-25, 1.3217328, 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000] 634 | >>> assert r.record(1) == [None, None, None, None, None, None] 635 | 636 | 637 | Finally, we can create boolean fields by setting the type to 'L'. 638 | This field can take True or False values, or 1 (True) or 0 (False). 639 | None is interpreted as missing. 640 | 641 | 642 | >>> w = shapefile.Writer() 643 | >>> w.field('BOOLEAN', 'L') 644 | >>> w.null() 645 | >>> w.null() 646 | >>> w.null() 647 | >>> w.null() 648 | >>> w.null() 649 | >>> w.null() 650 | >>> w.record(True) 651 | >>> w.record(1) 652 | >>> w.record(False) 653 | >>> w.record(0) 654 | >>> w.record(None) 655 | >>> w.record("Nonesense") 656 | >>> w.save('shapefiles/test/dtype') 657 | 658 | >>> r = shapefile.Reader('shapefiles/test/dtype') 659 | >>> r.record(0) 660 | [True] 661 | >>> r.record(1) 662 | [True] 663 | >>> r.record(2) 664 | [False] 665 | >>> r.record(3) 666 | [False] 667 | >>> r.record(4) 668 | [None] 669 | >>> r.record(5) 670 | [None] 671 | 672 | You can also add attributes using keyword arguments where the keys are field names. 673 | 674 | 675 | >>> w = shapefile.Writer() 676 | >>> w.field('FIRST_FLD','C','40') 677 | >>> w.field('SECOND_FLD','C','40') 678 | >>> w.record('First', 'Line') 679 | >>> w.record(FIRST_FLD='First', SECOND_FLD='Line') 680 | 681 | 682 | 683 | ### File Names 684 | 685 | File extensions are optional when reading or writing shapefiles. If you specify 686 | them PyShp ignores them anyway. When you save files you can specify a base 687 | file name that is used for all three file types. Or you can specify a name for 688 | one or more file types. In that case, any file types not assigned will not 689 | save and only file types with file names will be saved. If you do not specify 690 | any file names (i.e. save()), then a unique file name is generated with the 691 | prefix "shapefile_" followed by random characters which is used for all three 692 | files. The unique file name is returned as a string. 693 | 694 | 695 | >>> targetName = w.save() 696 | >>> assert("shapefile_" in targetName) 697 | 698 | ### Saving to File-Like Objects 699 | 700 | Just as you can read shapefiles from python file-like objects you can also 701 | write them. 702 | 703 | 704 | >>> try: 705 | ... from StringIO import StringIO 706 | ... except ImportError: 707 | ... from io import BytesIO as StringIO 708 | >>> shp = StringIO() 709 | >>> shx = StringIO() 710 | >>> dbf = StringIO() 711 | >>> w.saveShp(shp) 712 | >>> w.saveShx(shx) 713 | >>> w.saveDbf(dbf) 714 | >>> # Normally you would call the "StringIO.getvalue()" method on these objects. 715 | >>> shp = shx = dbf = None 716 | 717 | ## Working with Large Shapefiles 718 | 719 | Despite being a lightweight library, PyShp is designed to be able to read and write 720 | shapefiles of any size, allowing you to work with hundreds of thousands or even millions 721 | of records and complex geometries. 722 | 723 | When first creating the Reader class, the library only reads the header information 724 | and leaves the rest of the file contents alone. Once you call the records() and shapes() 725 | methods however, it will attempt to read the entire file into memory at once. 726 | For very large files this can result in MemoryError. So when working with large files 727 | it is recommended to use instead the iterShapes(), iterRecords(), or iterShapeRecords() 728 | methods instead. These iterate through the file contents one at a time, enabling you to loop 729 | through them while keeping memory usage at a minimum. 730 | 731 | 732 | >>> for shape in sf.iterShapes(): 733 | ... # do something here 734 | ... pass 735 | 736 | >>> for rec in sf.iterRecords(): 737 | ... # do something here 738 | ... pass 739 | 740 | >>> for shapeRec in sf.iterShapeRecords(): 741 | ... # do something here 742 | ... pass 743 | 744 | The shapefile Writer class uses a similar streaming approach to keep memory 745 | usage at a minimum, except you don't have change any of your code. 746 | The library takes care of this under-the-hood by creating a set of temporary files 747 | and immediately writing each geometry and record to disk the moment they 748 | are added using shape() or record(). You still have to call save() as usual 749 | in order to specify the final location of the output file and in order 750 | for the header information to be calculated and written to the beginning of the 751 | file. 752 | 753 | This means that as long as you are able to iterate through a source file without having 754 | to load everything into memory, such as a large CSV table or a large shapefile, you can 755 | process and write any number of items, and even merging many different source files into a single 756 | large shapefile. If you need to edit or undo any of your writing you would have to read the 757 | file back in, one record at a time, make your changes, and write it back out. 758 | 759 | ## Python Geo Interface 760 | 761 | The Python \_\_geo_interface\_\_ convention provides a data interchange interface 762 | among geospatial Python libraries. The interface returns data as GeoJSON which gives you 763 | nice compatibility with other libraries and tools including Shapely, Fiona, and PostGIS. 764 | More information on the \_\_geo_interface\_\_ protocol can be found at: 765 | [https://gist.github.com/sgillies/2217756](https://gist.github.com/sgillies/2217756). 766 | More information on GeoJSON is available at [http://geojson.org](http://geojson.org). 767 | 768 | 769 | >>> s = sf.shape(0) 770 | >>> s.__geo_interface__["type"] 771 | 'MultiPolygon' 772 | 773 | Just as the library can expose its objects to other applications through the geo interface, 774 | it also supports receiving objects with the geo interface from other applications. 775 | To write shapes based on GeoJSON objects, simply send an object with the geo interface or a 776 | GeoJSON dictionary to the shape() method instead of a Shape object. Alternatively, you can 777 | construct a Shape object from GeoJSON using the "geojson_as_shape()" function. 778 | 779 | 780 | >>> w = shapefile.Writer() 781 | >>> w.field('name', 'C') 782 | 783 | >>> w.shape( {"type":"Point", "coordinates":[1,1]} ) 784 | >>> w.record('one') 785 | 786 | >>> shape = shapefile.geojson_to_shape( {"type":"Point", "coordinates":[2,2]} ) 787 | >>> shape.shapeType 788 | 1 789 | >>> shape.points 790 | [[2, 2]] 791 | 792 | >>> w.shape(shape) 793 | >>> w.record('two') 794 | 795 | >>> w.save('shapefiles/test/geojson') 796 | 797 | # Testing 798 | 799 | The testing framework is doctest, which are located in this file README.md. 800 | In the same folder as README.md and shapefile.py, from the command line run 801 | ``` 802 | $ python shapefile.py 803 | ``` 804 | 805 | Linux/Mac and similar platforms will need to run `$ dos2unix README.md` in order 806 | correct line endings in README.md. 807 | -------------------------------------------------------------------------------- /shapefile.py: -------------------------------------------------------------------------------- 1 | """ 2 | shapefile.py 3 | Provides read and write support for ESRI Shapefiles. 4 | author: jlawheadgeospatialpython.com 5 | date: 2017/04/29 6 | version: 1.2.11 7 | Compatible with Python versions 2.7-3.x 8 | """ 9 | 10 | __version__ = "1.2.11" 11 | 12 | from struct import pack, unpack, calcsize, error, Struct 13 | import os 14 | import sys 15 | import time 16 | import array 17 | import tempfile 18 | import itertools 19 | import io 20 | from datetime import date 21 | 22 | 23 | # Constants for shape types 24 | NULL = 0 25 | POINT = 1 26 | POLYLINE = 3 27 | POLYGON = 5 28 | MULTIPOINT = 8 29 | POINTZ = 11 30 | POLYLINEZ = 13 31 | POLYGONZ = 15 32 | MULTIPOINTZ = 18 33 | POINTM = 21 34 | POLYLINEM = 23 35 | POLYGONM = 25 36 | MULTIPOINTM = 28 37 | MULTIPATCH = 31 38 | 39 | 40 | # Python 2-3 handling 41 | 42 | PYTHON3 = sys.version_info[0] == 3 43 | 44 | if PYTHON3: 45 | xrange = range 46 | izip = zip 47 | else: 48 | from itertools import izip 49 | 50 | 51 | # Helpers 52 | 53 | MISSING = [None,''] 54 | 55 | if PYTHON3: 56 | def b(v): 57 | if isinstance(v, str): 58 | # For python 3 encode str to bytes. 59 | return v.encode('utf-8') 60 | elif isinstance(v, bytes): 61 | # Already bytes. 62 | return v 63 | else: 64 | # Error. 65 | raise Exception('Unknown input type') 66 | 67 | def u(v): 68 | # try/catch added 2014/05/07 69 | # returned error on dbf of shapefile 70 | # from www.naturalearthdata.com named 71 | # "ne_110m_admin_0_countries". 72 | # Just returning v as is seemed to fix 73 | # the problem. This function could 74 | # be condensed further. 75 | try: 76 | if isinstance(v, bytes): 77 | # For python 3 decode bytes to str. 78 | return v.decode('utf-8') 79 | elif isinstance(v, str): 80 | # Already str. 81 | return v 82 | else: 83 | # Error. 84 | raise Exception('Unknown input type') 85 | except: return v 86 | 87 | def is_string(v): 88 | return isinstance(v, str) 89 | 90 | else: 91 | def b(v): 92 | # For python 2 assume str passed in and return str. 93 | return v 94 | 95 | def u(v): 96 | # For python 2 assume str passed in and return str. 97 | return v 98 | 99 | def is_string(v): 100 | return isinstance(v, basestring) 101 | 102 | 103 | # Begin 104 | 105 | class _Array(array.array): 106 | """Converts python tuples to lits of the appropritate type. 107 | Used to unpack different shapefile header parts.""" 108 | def __repr__(self): 109 | return str(self.tolist()) 110 | 111 | def signed_area(coords): 112 | """Return the signed area enclosed by a ring using the linear time 113 | algorithm. A value >= 0 indicates a counter-clockwise oriented ring. 114 | """ 115 | xs, ys = map(list, zip(*coords)) 116 | xs.append(xs[1]) 117 | ys.append(ys[1]) 118 | return sum(xs[i]*(ys[i+1]-ys[i-1]) for i in range(1, len(coords)))/2.0 119 | 120 | def geojson_to_shape(geoj): 121 | # create empty shape 122 | shape = Shape() 123 | # set shapeType 124 | geojType = geoj["type"] if geoj else "Null" 125 | if geojType == "Null": 126 | shapeType = NULL 127 | elif geojType == "Point": 128 | shapeType = POINT 129 | elif geojType == "LineString": 130 | shapeType = POLYLINE 131 | elif geojType == "Polygon": 132 | shapeType = POLYGON 133 | elif geojType == "MultiPoint": 134 | shapeType = MULTIPOINT 135 | elif geojType == "MultiLineString": 136 | shapeType = POLYLINE 137 | elif geojType == "MultiPolygon": 138 | shapeType = POLYGON 139 | else: 140 | raise Exception("Cannot create Shape from GeoJSON type '%s'" % geojType) 141 | shape.shapeType = shapeType 142 | 143 | # set points and parts 144 | if geojType == "Point": 145 | shape.points = [ geoj["coordinates"] ] 146 | shape.parts = [0] 147 | elif geojType in ("MultiPoint","LineString"): 148 | shape.points = geoj["coordinates"] 149 | shape.parts = [0] 150 | elif geojType in ("Polygon"): 151 | points = [] 152 | parts = [] 153 | index = 0 154 | for ext_or_hole in geoj["coordinates"]: 155 | points.extend(ext_or_hole) 156 | parts.append(index) 157 | index += len(ext_or_hole) 158 | shape.points = points 159 | shape.parts = parts 160 | elif geojType in ("MultiLineString"): 161 | points = [] 162 | parts = [] 163 | index = 0 164 | for linestring in geoj["coordinates"]: 165 | points.extend(linestring) 166 | parts.append(index) 167 | index += len(linestring) 168 | shape.points = points 169 | shape.parts = parts 170 | elif geojType in ("MultiPolygon"): 171 | points = [] 172 | parts = [] 173 | index = 0 174 | for polygon in geoj["coordinates"]: 175 | for ext_or_hole in polygon: 176 | points.extend(ext_or_hole) 177 | parts.append(index) 178 | index += len(ext_or_hole) 179 | shape.points = points 180 | shape.parts = parts 181 | return shape 182 | 183 | class Shape: 184 | def __init__(self, shapeType=NULL, points=None, parts=None, partTypes=None): 185 | """Stores the geometry of the different shape types 186 | specified in the Shapefile spec. Shape types are 187 | usually point, polyline, or polygons. Every shape type 188 | except the "Null" type contains points at some level for 189 | example verticies in a polygon. If a shape type has 190 | multiple shapes containing points within a single 191 | geometry record then those shapes are called parts. Parts 192 | are designated by their starting index in geometry record's 193 | list of shapes. For MultiPatch geometry, partTypes designates 194 | the patch type of each of the parts. 195 | """ 196 | self.shapeType = shapeType 197 | self.points = points or [] 198 | self.parts = parts or [] 199 | if partTypes: 200 | self.partTypes = partTypes 201 | 202 | @property 203 | def __geo_interface__(self): 204 | if self.shapeType in [POINT, POINTM, POINTZ]: 205 | return { 206 | 'type': 'Point', 207 | 'coordinates': tuple(self.points[0]) 208 | } 209 | elif self.shapeType in [MULTIPOINT, MULTIPOINTM, MULTIPOINTZ]: 210 | return { 211 | 'type': 'MultiPoint', 212 | 'coordinates': tuple([tuple(p) for p in self.points]) 213 | } 214 | elif self.shapeType in [POLYLINE, POLYLINEM, POLYLINEZ]: 215 | if len(self.parts) == 1: 216 | return { 217 | 'type': 'LineString', 218 | 'coordinates': tuple([tuple(p) for p in self.points]) 219 | } 220 | else: 221 | ps = None 222 | coordinates = [] 223 | for part in self.parts: 224 | if ps == None: 225 | ps = part 226 | continue 227 | else: 228 | coordinates.append(tuple([tuple(p) for p in self.points[ps:part]])) 229 | ps = part 230 | else: 231 | coordinates.append(tuple([tuple(p) for p in self.points[part:]])) 232 | return { 233 | 'type': 'MultiLineString', 234 | 'coordinates': tuple(coordinates) 235 | } 236 | elif self.shapeType in [POLYGON, POLYGONM, POLYGONZ]: 237 | if len(self.parts) == 1: 238 | return { 239 | 'type': 'Polygon', 240 | 'coordinates': (tuple([tuple(p) for p in self.points]),) 241 | } 242 | else: 243 | ps = None 244 | coordinates = [] 245 | for part in self.parts: 246 | if ps == None: 247 | ps = part 248 | continue 249 | else: 250 | coordinates.append(tuple([tuple(p) for p in self.points[ps:part]])) 251 | ps = part 252 | else: 253 | coordinates.append(tuple([tuple(p) for p in self.points[part:]])) 254 | polys = [] 255 | poly = [coordinates[0]] 256 | for coord in coordinates[1:]: 257 | if signed_area(coord) < 0: 258 | polys.append(poly) 259 | poly = [coord] 260 | else: 261 | poly.append(coord) 262 | polys.append(poly) 263 | if len(polys) == 1: 264 | return { 265 | 'type': 'Polygon', 266 | 'coordinates': tuple(polys[0]) 267 | } 268 | elif len(polys) > 1: 269 | return { 270 | 'type': 'MultiPolygon', 271 | 'coordinates': polys 272 | } 273 | 274 | class ShapeRecord: 275 | """A ShapeRecord object containing a shape along with its attributes.""" 276 | def __init__(self, shape=None, record=None): 277 | self.shape = shape 278 | self.record = record 279 | 280 | class ShapefileException(Exception): 281 | """An exception to handle shapefile specific problems.""" 282 | pass 283 | 284 | class Reader: 285 | """Reads the three files of a shapefile as a unit or 286 | separately. If one of the three files (.shp, .shx, 287 | .dbf) is missing no exception is thrown until you try 288 | to call a method that depends on that particular file. 289 | The .shx index file is used if available for efficiency 290 | but is not required to read the geometry from the .shp 291 | file. The "shapefile" argument in the constructor is the 292 | name of the file you want to open. 293 | 294 | You can instantiate a Reader without specifying a shapefile 295 | and then specify one later with the load() method. 296 | 297 | Only the shapefile headers are read upon loading. Content 298 | within each file is only accessed when required and as 299 | efficiently as possible. Shapefiles are usually not large 300 | but they can be. 301 | """ 302 | def __init__(self, *args, **kwargs): 303 | self.shp = None 304 | self.shx = None 305 | self.dbf = None 306 | self.shapeName = "Not specified" 307 | self._offsets = [] 308 | self.shpLength = None 309 | self.numRecords = None 310 | self.fields = [] 311 | self.__dbfHdrLength = 0 312 | # See if a shapefile name was passed as an argument 313 | if len(args) > 0: 314 | if is_string(args[0]): 315 | self.load(args[0]) 316 | return 317 | if "shp" in kwargs.keys(): 318 | if hasattr(kwargs["shp"], "read"): 319 | self.shp = kwargs["shp"] 320 | # Copy if required 321 | try: 322 | self.shp.seek(0) 323 | except (NameError, io.UnsupportedOperation): 324 | self.shp = io.BytesIO(self.shp.read()) 325 | if "shx" in kwargs.keys(): 326 | if hasattr(kwargs["shx"], "read"): 327 | self.shx = kwargs["shx"] 328 | # Copy if required 329 | try: 330 | self.shx.seek(0) 331 | except (NameError, io.UnsupportedOperation): 332 | self.shx = io.BytesIO(self.shx.read()) 333 | if "dbf" in kwargs.keys(): 334 | if hasattr(kwargs["dbf"], "read"): 335 | self.dbf = kwargs["dbf"] 336 | # Copy if required 337 | try: 338 | self.dbf.seek(0) 339 | except (NameError, io.UnsupportedOperation): 340 | self.dbf = io.BytesIO(self.dbf.read()) 341 | if self.shp or self.dbf: 342 | self.load() 343 | else: 344 | raise ShapefileException("Shapefile Reader requires a shapefile or file-like object.") 345 | 346 | def __len__(self): 347 | """Returns the number of shapes/records in the shapefile.""" 348 | return self.numRecords 349 | 350 | def load(self, shapefile=None): 351 | """Opens a shapefile from a filename or file-like 352 | object. Normally this method would be called by the 353 | constructor with the file name as an argument.""" 354 | if shapefile: 355 | (shapeName, ext) = os.path.splitext(shapefile) 356 | self.shapeName = shapeName 357 | try: 358 | self.shp = open("%s.shp" % shapeName, "rb") 359 | except IOError: 360 | pass 361 | try: 362 | self.shx = open("%s.shx" % shapeName, "rb") 363 | except IOError: 364 | pass 365 | try: 366 | self.dbf = open("%s.dbf" % shapeName, "rb") 367 | except IOError: 368 | pass 369 | if not (self.shp and self.dbf): 370 | raise ShapefileException("Unable to open %s.dbf or %s.shp." % (shapeName, shapeName) ) 371 | if self.shp: 372 | self.__shpHeader() 373 | if self.dbf: 374 | self.__dbfHeader() 375 | 376 | def __getFileObj(self, f): 377 | """Checks to see if the requested shapefile file object is 378 | available. If not a ShapefileException is raised.""" 379 | if not f: 380 | raise ShapefileException("Shapefile Reader requires a shapefile or file-like object.") 381 | if self.shp and self.shpLength is None: 382 | self.load() 383 | if self.dbf and len(self.fields) == 0: 384 | self.load() 385 | return f 386 | 387 | def __restrictIndex(self, i): 388 | """Provides list-like handling of a record index with a clearer 389 | error message if the index is out of bounds.""" 390 | if self.numRecords: 391 | rmax = self.numRecords - 1 392 | if abs(i) > rmax: 393 | raise IndexError("Shape or Record index out of range.") 394 | if i < 0: i = range(self.numRecords)[i] 395 | return i 396 | 397 | def __shpHeader(self): 398 | """Reads the header information from a .shp or .shx file.""" 399 | if not self.shp: 400 | raise ShapefileException("Shapefile Reader requires a shapefile or file-like object. (no shp file found") 401 | shp = self.shp 402 | # File length (16-bit word * 2 = bytes) 403 | shp.seek(24) 404 | self.shpLength = unpack(">i", shp.read(4))[0] * 2 405 | # Shape type 406 | shp.seek(32) 407 | self.shapeType= unpack("2i", f.read(8)) 421 | # Determine the start of the next record 422 | next = f.tell() + (2 * recLength) 423 | shapeType = unpack(" -10e38: 458 | record.m.append(m) 459 | else: 460 | record.m.append(None) 461 | # Read a single point 462 | if shapeType in (1,11,21): 463 | record.points = [_Array('d', unpack("<2d", f.read(16)))] 464 | # Read a single Z value 465 | if shapeType == 11: 466 | record.z = unpack("i", shx.read(4))[0] * 2) - 100 486 | numRecords = shxRecordLength // 8 487 | # Jump to the first record. 488 | shx.seek(100) 489 | shxRecords = _Array('i') 490 | # Each offset consists of two nrs, only the first one matters 491 | shxRecords.fromfile(shx, 2 * numRecords) 492 | if sys.byteorder != 'big': 493 | shxRecords.byteswap() 494 | self._offsets = [2 * el for el in shxRecords[::2]] 495 | if not i == None: 496 | return self._offsets[i] 497 | 498 | def shape(self, i=0): 499 | """Returns a shape object for a shape in the the geometry 500 | record file.""" 501 | shp = self.__getFileObj(self.shp) 502 | i = self.__restrictIndex(i) 503 | offset = self.__shapeIndex(i) 504 | if not offset: 505 | # Shx index not available so iterate the full list. 506 | for j,k in enumerate(self.iterShapes()): 507 | if j == i: 508 | return k 509 | shp.seek(offset) 510 | return self.__shape() 511 | 512 | def shapes(self): 513 | """Returns all shapes in a shapefile.""" 514 | shp = self.__getFileObj(self.shp) 515 | # Found shapefiles which report incorrect 516 | # shp file length in the header. Can't trust 517 | # that so we seek to the end of the file 518 | # and figure it out. 519 | shp.seek(0,2) 520 | self.shpLength = shp.tell() 521 | shp.seek(100) 522 | shapes = [] 523 | while shp.tell() < self.shpLength: 524 | shapes.append(self.__shape()) 525 | return shapes 526 | 527 | def iterShapes(self): 528 | """Serves up shapes in a shapefile as an iterator. Useful 529 | for handling large shapefiles.""" 530 | shp = self.__getFileObj(self.shp) 531 | shp.seek(0,2) 532 | self.shpLength = shp.tell() 533 | shp.seek(100) 534 | while shp.tell() < self.shpLength: 535 | yield self.__shape() 536 | 537 | def __dbfHeader(self): 538 | """Reads a dbf header. Xbase-related code borrows heavily from ActiveState Python Cookbook Recipe 362715 by Raymond Hettinger""" 539 | if not self.dbf: 540 | raise ShapefileException("Shapefile Reader requires a shapefile or file-like object. (no dbf file found)") 541 | dbf = self.dbf 542 | # read relevant header parts 543 | self.numRecords, self.__dbfHdrLength, self.__recordLength = \ 544 | unpack(" 0: 749 | px, py = list(zip(*s.points))[:2] 750 | x.extend(px) 751 | y.extend(py) 752 | if len(x) == 0: 753 | return [0] * 4 754 | bbox = [min(x), min(y), max(x), max(y)] 755 | # update global 756 | self._bbox = [min(bbox[0],self._bbox[0]), min(bbox[1],self._bbox[1]), max(bbox[2],self._bbox[2]), max(bbox[3],self._bbox[3])] 757 | return bbox 758 | 759 | def __zbox(self, s): 760 | z = [] 761 | try: 762 | for p in s.points: 763 | z.append(p[2]) 764 | except IndexError: 765 | pass 766 | if not z: z.append(0) 767 | zbox = [min(z), max(z)] 768 | # update global 769 | self._zbox = [min(zbox[0],self._zbox[0]), min(zbox[1],self._zbox[1]), max(zbox[2],self._zbox[2]), max(zbox[3],self._zbox[3])] 770 | return zbox 771 | 772 | def __mbox(self, shapes): 773 | m = [] 774 | try: 775 | for p in s.points: 776 | m.append(p[3]) 777 | except IndexError: 778 | pass 779 | if not m: m.append(0) 780 | mbox = [min(m), max(m)] 781 | # update global 782 | self._mbox = [min(mbox[0],self._mbox[0]), min(mbox[1],self._mbox[1]), max(mbox[2],self._mbox[2]), max(mbox[3],self._mbox[3])] 783 | return mbox 784 | 785 | def bbox(self): 786 | """Returns the current bounding box for the shapefile which is 787 | the lower-left and upper-right corners. It does not contain the 788 | elevation or measure extremes.""" 789 | return self._bbox 790 | 791 | def zbox(self): 792 | """Returns the current z extremes for the shapefile.""" 793 | return self._zbox 794 | 795 | def mbox(self): 796 | """Returns the current m extremes for the shapefile.""" 797 | return self._mbox 798 | 799 | def __shapefileHeader(self, fileObj, headerType='shp'): 800 | """Writes the specified header type to the specified file-like object. 801 | Several of the shapefile formats are so similar that a single generic 802 | method to read or write them is warranted.""" 803 | f = self.__getFileObj(fileObj) 804 | f.seek(0) 805 | # File code, Unused bytes 806 | f.write(pack(">6i", 9994,0,0,0,0,0)) 807 | # File length (Bytes / 2 = 16-bit words) 808 | if headerType == 'shp': 809 | f.write(pack(">i", self.__shpFileLength())) 810 | elif headerType == 'shx': 811 | f.write(pack('>i', ((100 + (self.shpNum * 8)) // 2))) 812 | # Version, Shape type 813 | if self.shapeType is None: 814 | self.shapeType = NULL 815 | f.write(pack("<2i", 1000, self.shapeType)) 816 | # The shapefile's bounding box (lower left, upper right) 817 | if self.shapeType != 0: 818 | try: 819 | f.write(pack("<4d", *self.bbox())) 820 | except error: 821 | raise ShapefileException("Failed to write shapefile bounding box. Floats required.") 822 | else: 823 | f.write(pack("<4d", 0,0,0,0)) 824 | # Elevation 825 | z = self.zbox() 826 | # Measure 827 | m = self.mbox() 828 | try: 829 | f.write(pack("<4d", z[0], z[1], m[0], m[1])) 830 | except error: 831 | raise ShapefileException("Failed to write shapefile elevation and measure values. Floats required.") 832 | 833 | def __dbfHeader(self): 834 | """Writes the dbf header and field descriptors.""" 835 | f = self.__getFileObj(self.dbf) 836 | f.seek(0) 837 | version = 3 838 | year, month, day = time.localtime()[:3] 839 | year -= 1900 840 | # Remove deletion flag placeholder from fields 841 | for field in self.fields: 842 | if str(field[0]).startswith("Deletion"): 843 | self.fields.remove(field) 844 | numRecs = self.recNum 845 | numFields = len(self.fields) 846 | headerLength = numFields * 32 + 33 847 | recordLength = sum([int(field[2]) for field in self.fields]) + 1 848 | header = pack('2i", self.shpNum, 0)) 887 | self.shpNum += 1 888 | start = f.tell() 889 | # Shape Type 890 | if self.shapeType is None and s.shapeType != NULL: 891 | self.shapeType = s.shapeType 892 | if self.shapeType != 31 and s.shapeType != NULL and s.shapeType != self.shapeType: 893 | raise Exception("The shape's type (%s) must match the type of the shapefile (%s)." % (s.shapeType, self.shapeType)) 894 | f.write(pack(" 3 and p[3] or 0)) for p in s.points] 947 | except error: 948 | raise ShapefileException("Failed to write measure values for record %s. Expected floats" % recNum) 949 | # Write a single point 950 | if s.shapeType in (1,11,21): 951 | try: 952 | f.write(pack("<2d", s.points[0][0], s.points[0][1])) 953 | except error: 954 | raise ShapefileException("Failed to write point for record %s. Expected floats." % recNum) 955 | # Write a single Z value 956 | if s.shapeType == 11: 957 | if hasattr(s, "z"): 958 | try: 959 | if not s.z: 960 | s.z = (0,) 961 | f.write(pack("i", length)) 993 | f.seek(finish) 994 | return offset,length 995 | 996 | def __shxRecord(self, offset, length): 997 | """Writes the shx records.""" 998 | f = self.__getFileObj(self._shx) 999 | f.write(pack(">i", offset // 2)) 1000 | f.write(pack(">i", length)) 1001 | 1002 | def record(self, *recordList, **recordDict): 1003 | """Creates a dbf attribute record. You can submit either a sequence of 1004 | field values or keyword arguments of field names and values. Before 1005 | adding records you must add fields for the record values using the 1006 | fields() method. If the record values exceed the number of fields the 1007 | extra ones won't be added. In the case of using keyword arguments to specify 1008 | field/value pairs only fields matching the already registered fields 1009 | will be added.""" 1010 | # Balance if already not balanced 1011 | if self.autoBalance and self.recNum > self.shpNum: 1012 | self.balance() 1013 | 1014 | record = [] 1015 | fieldCount = len(self.fields) 1016 | # Compensate for deletion flag 1017 | if self.fields[0][0].startswith("Deletion"): fieldCount -= 1 1018 | if recordList: 1019 | record = [recordList[i] for i in range(fieldCount)] 1020 | elif recordDict: 1021 | for field in self.fields: 1022 | if field[0] in recordDict: 1023 | val = recordDict[field[0]] 1024 | if val is None: 1025 | record.append("") 1026 | else: 1027 | record.append(val) 1028 | else: 1029 | # Blank fields for empty record 1030 | record = ["" for i in range(fieldCount)] 1031 | self.__dbfRecord(record) 1032 | 1033 | def __dbfRecord(self, record): 1034 | """Writes the dbf records.""" 1035 | f = self.__getFileObj(self._dbf) 1036 | self.recNum += 1 1037 | if not self.fields[0][0].startswith("Deletion"): 1038 | f.write(b(' ')) # deletion flag 1039 | for (fieldName, fieldType, size, deci), value in zip(self.fields, record): 1040 | fieldType = fieldType.upper() 1041 | size = int(size) 1042 | if fieldType in ("N","F"): 1043 | # numeric or float: number stored as a string, right justified, and padded with blanks to the width of the field. 1044 | if value in MISSING: 1045 | value = str("*"*size) # QGIS NULL 1046 | elif not deci: 1047 | value = format(value, "d")[:size].rjust(size) # caps the size if exceeds the field size 1048 | else: 1049 | value = format(value, ".%sf"%deci)[:size].rjust(size) # caps the size if exceeds the field size 1050 | elif fieldType == "D": 1051 | # date: 8 bytes - date stored as a string in the format YYYYMMDD. 1052 | if isinstance(value, date): 1053 | value = value.strftime("%Y%m%d") 1054 | elif isinstance(value, list) and len(value) == 3: 1055 | value = date(*value).strftime("%Y%m%d") 1056 | elif value in MISSING: 1057 | value = b('0') * 8 # QGIS NULL for date type 1058 | elif isinstance(value, str) and len(value) == 8: 1059 | pass # value is already a date string 1060 | else: 1061 | raise ShapefileException("Date values must be either a datetime.date object, a list, a YYYYMMDD string, or a missing value.") 1062 | elif fieldType == 'L': 1063 | # logical: 1 byte - initialized to 0x20 (space) otherwise T or F. 1064 | if value in MISSING: 1065 | value = b(' ') # missing is set to space 1066 | elif value in [True,1]: 1067 | value = b("T") 1068 | elif value in [False,0]: 1069 | value = b("F") 1070 | else: 1071 | value = b(' ') # unknown is set to space 1072 | else: 1073 | # anything else is forced to string 1074 | value = str(value)[:size].ljust(size) 1075 | if len(value) != size: 1076 | raise ShapefileException( 1077 | "Shapefile Writer unable to pack incorrect sized value" 1078 | " (size %d) into field '%s' (size %d)." % (len(value), fieldName, size)) 1079 | value = b(value) 1080 | f.write(value) 1081 | 1082 | def balance(self): 1083 | """Adds corresponding empty attributes or null geometry records depending 1084 | on which type of record was created to make sure all three files 1085 | are in synch.""" 1086 | while self.recNum > self.shpNum: 1087 | self.null() 1088 | while self.recNum < self.shpNum: 1089 | self.record() 1090 | 1091 | def null(self): 1092 | """Creates a null shape.""" 1093 | self.shape(Shape(NULL)) 1094 | 1095 | def point(self, x, y, z=0, m=0, shapeType=POINT): 1096 | """Creates a point shape.""" 1097 | pointShape = Shape(shapeType) 1098 | if shapeType == POINT: 1099 | pointShape.points.append([x, y]) 1100 | elif shapeType == POINTZ: 1101 | pointShape.points.append([x, y, z]) 1102 | elif shapeType == POINTM: 1103 | pointShape.points.append([x, y, z, m]) 1104 | self.shape(pointShape) 1105 | 1106 | def line(self, parts=[], shapeType=POLYLINE): 1107 | """Creates a line shape. This method is just a convienience method 1108 | which wraps 'poly()'. 1109 | """ 1110 | self.poly(parts, shapeType, []) 1111 | 1112 | def poly(self, parts=[], shapeType=POLYGON, partTypes=[]): 1113 | """Creates a shape that has multiple collections of points (parts) 1114 | including lines, polygons, and even multipoint shapes. If no shape type 1115 | is specified it defaults to 'polygon'. If no part types are specified 1116 | (which they normally won't be) then all parts default to the shape type. 1117 | """ 1118 | polyShape = Shape(shapeType) 1119 | polyShape.parts = [] 1120 | polyShape.points = [] 1121 | # Make sure polygons are closed 1122 | if shapeType in (5,15,25,31): 1123 | for part in parts: 1124 | if part[0] != part[-1]: 1125 | part.append(part[0]) 1126 | for part in parts: 1127 | polyShape.parts.append(len(polyShape.points)) 1128 | for point in part: 1129 | # Ensure point is list 1130 | if not isinstance(point, list): 1131 | point = list(point) 1132 | # Make sure point has z and m values 1133 | while len(point) < 4: 1134 | point.append(0) 1135 | polyShape.points.append(point) 1136 | if polyShape.shapeType == 31: 1137 | if not partTypes: 1138 | for part in parts: 1139 | partTypes.append(polyShape.shapeType) 1140 | polyShape.partTypes = partTypes 1141 | self.shape(polyShape) 1142 | 1143 | def field(self, name, fieldType="C", size="50", decimal=0): 1144 | """Adds a dbf field descriptor to the shapefile.""" 1145 | if fieldType == "D": 1146 | size = "8" 1147 | decimal = 0 1148 | elif fieldType == "L": 1149 | size = "1" 1150 | decimal = 0 1151 | self.fields.append((name, fieldType, size, decimal)) 1152 | 1153 | def saveShp(self, target): 1154 | """Save an shp file.""" 1155 | if not hasattr(target, "write"): 1156 | target = os.path.splitext(target)[0] + '.shp' 1157 | self.shp = self.__getFileObj(target) 1158 | self.__shapefileHeader(self.shp, headerType='shp') 1159 | self.shp.seek(100) 1160 | self._shp.seek(0) 1161 | chunk = True 1162 | while chunk: 1163 | chunk = self._shp.read(self.bufsize) 1164 | self.shp.write(chunk) 1165 | 1166 | def saveShx(self, target): 1167 | """Save an shx file.""" 1168 | if not hasattr(target, "write"): 1169 | target = os.path.splitext(target)[0] + '.shx' 1170 | self.shx = self.__getFileObj(target) 1171 | self.__shapefileHeader(self.shx, headerType='shx') 1172 | self.shx.seek(100) 1173 | self._shx.seek(0) 1174 | chunk = True 1175 | while chunk: 1176 | chunk = self._shx.read(self.bufsize) 1177 | self.shx.write(chunk) 1178 | 1179 | def saveDbf(self, target): 1180 | """Save a dbf file.""" 1181 | if not hasattr(target, "write"): 1182 | target = os.path.splitext(target)[0] + '.dbf' 1183 | self.dbf = self.__getFileObj(target) 1184 | self.__dbfHeader() # writes to .dbf 1185 | self._dbf.seek(0) 1186 | chunk = True 1187 | while chunk: 1188 | chunk = self._dbf.read(self.bufsize) 1189 | self.dbf.write(chunk) 1190 | 1191 | def save(self, target=None, shp=None, shx=None, dbf=None): 1192 | """Save the shapefile data to three files or 1193 | three file-like objects. SHP and DBF files can also 1194 | be written exclusively using saveShp, saveShx, and saveDbf respectively. 1195 | If target is specified but not shp, shx, or dbf then the target path and 1196 | file name are used. If no options or specified, a unique base file name 1197 | is generated to save the files and the base file name is returned as a 1198 | string. 1199 | """ 1200 | # Balance if already not balanced 1201 | if shp and dbf: 1202 | if self.autoBalance: 1203 | self.balance() 1204 | if self.recNum != self.shpNum: 1205 | raise ShapefileException("When saving both the dbf and shp file, " 1206 | "the number of records (%s) must correspond " 1207 | "with the number of shapes (%s)" % (self.recNum, self.shpNum)) 1208 | # Save 1209 | if shp: 1210 | self.saveShp(shp) 1211 | if shx: 1212 | self.saveShx(shx) 1213 | if dbf: 1214 | self.saveDbf(dbf) 1215 | # Create a unique file name if one is not defined 1216 | if not shp and not shx and not dbf: 1217 | generated = False 1218 | if not target: 1219 | temp = tempfile.NamedTemporaryFile(prefix="shapefile_",dir=os.getcwd()) 1220 | target = temp.name 1221 | generated = True 1222 | self.saveShp(target) 1223 | self.shp.close() 1224 | self.saveShx(target) 1225 | self.shx.close() 1226 | self.saveDbf(target) 1227 | self.dbf.close() 1228 | if generated: 1229 | return target 1230 | 1231 | # Begin Testing 1232 | def test(**kwargs): 1233 | import doctest 1234 | doctest.NORMALIZE_WHITESPACE = 1 1235 | verbosity = kwargs.get('verbose', 0) 1236 | if verbosity == 0: 1237 | print('Running doctests...') 1238 | failure_count, test_count = doctest.testfile("README.md", verbose=verbosity) 1239 | if verbosity == 0 and failure_count == 0: 1240 | print('All test passed successfully') 1241 | return failure_count 1242 | 1243 | if __name__ == "__main__": 1244 | """ 1245 | Doctests are contained in the file 'README.md'. This library was originally developed 1246 | using Python 2.3. Python 2.4 and above have some excellent improvements in the built-in 1247 | testing libraries but for now unit testing is done using what's available in 1248 | 2.3. 1249 | """ 1250 | failure_count = test() 1251 | sys.exit(failure_count) 1252 | --------------------------------------------------------------------------------