From 5dc5fda50204336f6f791618cf6b4d94911bc850 Mon Sep 17 00:00:00 2001 From: JamesParrott <80779630+JamesParrott@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:41:27 +0100 Subject: [PATCH 1/3] Create .pre-commit-config.yaml --- .pre-commit-config.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..18f0b5d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: trailing-whitespace +- repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + name: isort (python) + From d759b98e11400d984ec4914dda915c1c94bd10c6 Mon Sep 17 00:00:00 2001 From: JamesParrott <80779630+JamesParrott@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:03:26 +0100 Subject: [PATCH 2/3] Run pre-commit hooks from build.yml Github Actions workflow --- .github/workflows/build.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dfeae58..b9f5895 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,13 @@ on: workflow_dispatch: jobs: - build: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - uses: pre-commit/action@v3.0.1 + test: strategy: fail-fast: false From 677bce00a82f54af44088310771d43e9672d9f85 Mon Sep 17 00:00:00 2001 From: JamesParrott <80779630+JamesParrott@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:07:01 +0100 Subject: [PATCH 3/3] Run pre-commit hooks locally first --- .github/ISSUE_TEMPLATE/bug.yml | 8 +- .github/ISSUE_TEMPLATE/newfeature.yml | 2 +- .github/ISSUE_TEMPLATE/question.yml | 2 +- .github/ISSUE_TEMPLATE/unexpected.yml | 8 +- LICENSE.TXT | 2 +- README.md | 408 +++++++++++++------------- shapefile.py | 11 +- test_shapefile.py | 3 +- 8 files changed, 222 insertions(+), 222 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index abd5383..aa7e47b 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -7,7 +7,7 @@ body: id: pyshp-version attributes: label: PyShp Version - description: Please input the version of PyShp you used. If unsure, call `shapefile.__version__`. + description: Please input the version of PyShp you used. If unsure, call `shapefile.__version__`. placeholder: ... validations: required: true @@ -15,7 +15,7 @@ body: id: python-version attributes: label: Python Version - description: Please input the version of the Python executable. + description: Please input the version of the Python executable. placeholder: ... validations: required: true @@ -23,7 +23,7 @@ body: id: your-code attributes: label: Your code - description: Please copy-paste the relevant parts of your code or script that triggered the error. + description: Please copy-paste the relevant parts of your code or script that triggered the error. placeholder: ... render: shell validations: @@ -41,7 +41,7 @@ body: id: notes attributes: label: Other notes - description: Please input any other notes that may be relevant, e.g. do you have any thoughts on what might be wrong? + description: Please input any other notes that may be relevant, e.g. do you have any thoughts on what might be wrong? placeholder: ... validations: required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/newfeature.yml b/.github/ISSUE_TEMPLATE/newfeature.yml index f35326d..afb043a 100644 --- a/.github/ISSUE_TEMPLATE/newfeature.yml +++ b/.github/ISSUE_TEMPLATE/newfeature.yml @@ -15,7 +15,7 @@ body: id: contribute attributes: label: Contributions - description: Would you be interested to contribute code that adds this functionality through a Pull Request? We gladly accept PRs - it's much faster and you'll be added a contributor. + description: Would you be interested to contribute code that adds this functionality through a Pull Request? We gladly accept PRs - it's much faster and you'll be added a contributor. options: - label: I am interested in implementing the described feature request and submit as a PR. required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 76dfb68..d8c0cd0 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -6,7 +6,7 @@ body: - type: textarea id: question attributes: - label: What's your question? + label: What's your question? description: Please describe what you would like to know about PyShp, e.g. how to do something. placeholder: ... validations: diff --git a/.github/ISSUE_TEMPLATE/unexpected.yml b/.github/ISSUE_TEMPLATE/unexpected.yml index 07ed85c..bf0a577 100644 --- a/.github/ISSUE_TEMPLATE/unexpected.yml +++ b/.github/ISSUE_TEMPLATE/unexpected.yml @@ -7,7 +7,7 @@ body: id: pyshp-version attributes: label: PyShp Version - description: Please input the version of PyShp you used. If unsure, call `shapefile.__version__`. + description: Please input the version of PyShp you used. If unsure, call `shapefile.__version__`. placeholder: ... validations: required: true @@ -15,7 +15,7 @@ body: id: python-version attributes: label: Python Version - description: Please input the version of the Python executable. + description: Please input the version of the Python executable. placeholder: ... validations: required: true @@ -23,7 +23,7 @@ body: id: your-code attributes: label: Your code - description: Please copy-paste the relevant parts of your code or script that you tried to run. + description: Please copy-paste the relevant parts of your code or script that you tried to run. placeholder: ... render: shell validations: @@ -48,7 +48,7 @@ body: id: notes attributes: label: Other notes - description: Please input any other notes that may be relevant, e.g. do you have any thoughts on what might be wrong? + description: Please input any other notes that may be relevant, e.g. do you have any thoughts on what might be wrong? placeholder: ... validations: required: false \ No newline at end of file diff --git a/LICENSE.TXT b/LICENSE.TXT index ce33f7b..d2b7446 100644 --- a/LICENSE.TXT +++ b/LICENSE.TXT @@ -1,5 +1,5 @@ The MIT License (MIT) - + Copyright © 2013 Joel Lawhead Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 94861bb..8bfc869 100644 --- a/README.md +++ b/README.md @@ -76,14 +76,14 @@ despite the numerous ways to store and exchange GIS data available today. Pyshp is compatible with Python 2.7-3.x. -This document provides examples for using PyShp to read and write shapefiles. However +This document provides examples for using PyShp to read and write shapefiles. However many more examples are continually added to the blog [http://GeospatialPython.com](http://GeospatialPython.com), -and by searching for PyShp on [https://gis.stackexchange.com](https://gis.stackexchange.com). +and by searching for PyShp on [https://gis.stackexchange.com](https://gis.stackexchange.com). Currently the sample census blockgroup shapefile referenced in the examples is available on the GitHub project site at [https://github.com/GeospatialPython/pyshp](https://github.com/GeospatialPython/pyshp). These examples are straight-forward and you can also easily run them against your -own shapefiles with minimal modification. +own shapefiles with minimal modification. Important: If you are new to GIS you should read about map projections. Please visit: [https://github.com/GeospatialPython/pyshp/wiki/Map-Projections](https://github.com/GeospatialPython/pyshp/wiki/Map-Projections) @@ -105,7 +105,7 @@ part of your geospatial project. ### New Features: -- Added support for pathlib and path-like shapefile filepaths (@mwtoews). +- Added support for pathlib and path-like shapefile filepaths (@mwtoews). - Allow reading individual file extensions via filepaths. ### Improvements: @@ -119,7 +119,7 @@ part of your geospatial project. - More robust handling of corrupt shapefiles (fixes #235) - Fix errors when writing to individual file-handles (fixes #237) - Revert previous decision to enforce geojson output ring orientation (detailed explanation at https://github.com/SciTools/cartopy/issues/2012) -- Fix test issues in environments without network access (@sebastic, @musicinmybrain). +- Fix test issues in environments without network access (@sebastic, @musicinmybrain). ## 2.2.0 @@ -132,7 +132,7 @@ part of your geospatial project. ### Improvements: -- More examples and restructuring of README. +- More examples and restructuring of README. - More informative Shape to geojson warnings (see #219). - Add shapefile.VERBOSE flag to control warnings verbosity (default True). - Shape object information when calling repr(). @@ -189,7 +189,7 @@ part of your geospatial project. ### New Features: -- Added back read/write support for unicode field names. +- Added back read/write support for unicode field names. - Improved Record representation - More support for geojson on Reader, ShapeRecord, ShapeRecords, and shapes() @@ -201,38 +201,38 @@ part of your geospatial project. ## 2.0.0 -The newest version of PyShp, version 2.0 introduced some major new improvements. +The newest version of PyShp, version 2.0 introduced some major new improvements. A great thanks to all who have contributed code and raised issues, and for everyone's -patience and understanding during the transition period. -Some of the new changes are incompatible with previous versions. +patience and understanding during the transition period. +Some of the new changes are incompatible with previous versions. Users of the previous version 1.x should therefore take note of the following changes -(Note: Some contributor attributions may be missing): +(Note: Some contributor attributions may be missing): ### Major Changes: -- Full support for unicode text, with custom encoding, and exception handling. - - Means that the Reader returns unicode, and the Writer accepts unicode. -- PyShp has been simplified to a pure input-output library using the Reader and Writer classes, dropping the Editor class. +- Full support for unicode text, with custom encoding, and exception handling. + - Means that the Reader returns unicode, and the Writer accepts unicode. +- PyShp has been simplified to a pure input-output library using the Reader and Writer classes, dropping the Editor class. - Switched to a new streaming approach when writing files, keeping memory-usage at a minimum: - - Specify filepath/destination and text encoding when creating the Writer. - - The file is written incrementally with each call to shape/record. - - Adding shapes is now done using dedicated methods for each shapetype. + - Specify filepath/destination and text encoding when creating the Writer. + - The file is written incrementally with each call to shape/record. + - Adding shapes is now done using dedicated methods for each shapetype. - Reading shapefiles is now more convenient: - - Shapefiles can be opened using the context manager, and files are properly closed. - - Shapefiles can be iterated, have a length, and supports the geo interface. + - Shapefiles can be opened using the context manager, and files are properly closed. + - Shapefiles can be iterated, have a length, and supports the geo interface. - New ways of inspecting shapefile metadata by printing. [@megies] - More convenient accessing of Record values as attributes. [@philippkraft] - - More convenient shape type name checking. [@megies] -- Add more support and documentation for MultiPatch 3D shapes. -- The Reader "elevation" and "measure" attributes now renamed "zbox" and "mbox", to make it clear they refer to the min/max values. -- Better documentation of previously unclear aspects, such as field types. + - More convenient shape type name checking. [@megies] +- Add more support and documentation for MultiPatch 3D shapes. +- The Reader "elevation" and "measure" attributes now renamed "zbox" and "mbox", to make it clear they refer to the min/max values. +- Better documentation of previously unclear aspects, such as field types. ### Important Fixes: - More reliable/robust: - Fixed shapefile bbox error for empty or point type shapefiles. [@mcuprjak] - Reading and writing Z and M type shapes is now more robust, fixing many errors, and has been added to the documentation. [@ShinNoNoir] - - Improved parsing of field value types, fixed errors and made more flexible. + - Improved parsing of field value types, fixed errors and made more flexible. - Fixed bug when writing shapefiles with datefield and date values earlier than 1900 [@megies] - Fix some geo interface errors, including checking polygon directions. - Bug fixes for reading from case sensitive file names, individual files separately, and from file-like objects. [@gastoneb, @kb003308, @erickskb] @@ -275,7 +275,7 @@ OR >>> sf = shapefile.Reader("shapefiles/blockgroups.dbf") OR any of the other 5+ formats which are potentially part of a shapefile. The -library does not care about file extensions. You can also specify that you only +library does not care about file extensions. You can also specify that you only want to read some of the file extensions through the use of keyword arguments: @@ -283,7 +283,7 @@ want to read some of the file extensions through the use of keyword arguments: #### Reading Shapefiles from Zip Files -If your shapefile is wrapped inside a zip file, the library is able to handle that too, meaning you don't have to worry about unzipping the contents: +If your shapefile is wrapped inside a zip file, the library is able to handle that too, meaning you don't have to worry about unzipping the contents: >>> sf = shapefile.Reader("shapefiles/blockgroups.zip") @@ -295,7 +295,7 @@ If the zip file contains multiple shapefiles, just specify which shapefile to re #### Reading Shapefiles from URLs -Finally, you can use all of the above methods to read shapefiles directly from the internet, by giving a url instead of a local path, e.g.: +Finally, you can use all of the above methods to read shapefiles directly from the internet, by giving a url instead of a local path, e.g.: >>> # from a zipped shapefile on website @@ -337,8 +337,8 @@ objects are properly closed when done reading the data: #### Reading Shapefile Meta-Data Shapefiles have a number of attributes for inspecting the file contents. -A shapefile is a container for a specific type of geometry, and this can be checked using the -shapeType attribute. +A shapefile is a container for a specific type of geometry, and this can be checked using the +shapeType attribute. >>> sf = shapefile.Reader("shapefiles/blockgroups.dbf") @@ -364,7 +364,7 @@ the existing shape types are not sequential: - POLYGONM = 25 - MULTIPOINTM = 28 - MULTIPATCH = 31 - + Based on this we can see that our blockgroups shapefile contains Polygon type shapes. The shape types are also defined as constants in the shapefile module, so that we can compare types more intuitively: @@ -378,8 +378,8 @@ For convenience, you can also get the name of the shape type as a string: >>> sf.shapeTypeName == 'POLYGON' True - -Other pieces of meta-data that we can check include the number of features + +Other pieces of meta-data that we can check include the number of features and the bounding box area the shapefile covers: @@ -387,10 +387,10 @@ and the bounding box area the shapefile covers: 663 >>> sf.bbox [-122.515048, 37.652916, -122.327622, 37.863433] - + Finally, if you would prefer to work with the entire shapefile in a different format, you can convert all of it to a GeoJSON dictionary, although you may lose -some information in the process, such as z- and m-values: +some information in the process, such as z- and m-values: >>> sf.__geo_interface__['type'] @@ -415,7 +415,7 @@ each shape record. >>> len(shapes) 663 - + To read a single shape by calling its index use the shape() method. The index is the shape's count from 0. So to read the 8th shape record you would use its index which is 7. @@ -457,12 +457,12 @@ shapeType Point do not have a bounding box 'bbox'. >>> shapes[3].shapeType 5 - * `shapeTypeName`: a string representation of the type of shape as defined by shapeType. Read-only. + * `shapeTypeName`: a string representation of the type of shape as defined by shapeType. Read-only. >>> shapes[3].shapeTypeName 'POLYGON' - + * `bbox`: If the shape type contains multiple points this tuple describes the lower left (x,y) coordinate and upper right corner coordinate creating a complete box around the points. If the shapeType is a @@ -496,7 +496,7 @@ shapeType Point do not have a bounding box 'bbox'. >>> ['%.3f' % coord for coord in shape] ['-122.471', '37.787'] -In most cases, however, if you need to do more than just type or bounds checking, you may want +In most cases, however, if you need to do more than just type or bounds checking, you may want to convert the geometry to the more human-readable [GeoJSON format](http://geojson.org), where lines and polygons are grouped for you: @@ -505,7 +505,7 @@ where lines and polygons are grouped for you: >>> geoj = s.__geo_interface__ >>> geoj["type"] 'MultiPolygon' - + The results from the shapes() method similarly supports converting to GeoJSON: @@ -514,12 +514,12 @@ The results from the shapes() method similarly supports converting to GeoJSON: Note: In some cases, if the conversion from shapefile geometry to GeoJSON encountered any problems or potential issues, a warning message will be displayed with information about the affected -geometry. To ignore or suppress these warnings, you can disable this behavior by setting the -module constant VERBOSE to False: +geometry. To ignore or suppress these warnings, you can disable this behavior by setting the +module constant VERBOSE to False: >>> shapefile.VERBOSE = False - + ### Reading Records @@ -534,12 +534,12 @@ You can call the "fields" attribute of the shapefile as a Python list. Each field is a Python list with the following information: * Field name: the name describing the data at this column index. - * Field type: the type of data at this column index. Types can be: + * Field type: the type of data at this column index. Types can be: * "C": Characters, text. * "N": Numbers, with or without decimals. * "F": Floats (same as "N"). - * "L": Logical, for boolean True/False values. - * "D": Dates. + * "L": Logical, for boolean True/False values. + * "D": Dates. * "M": Memo, has no meaning within a GIS and is part of the xbase spec instead. * Field length: the length of the data found at this column index. Older GIS software may truncate this length to 8 or 11 characters for "Character" @@ -571,11 +571,11 @@ attribute: ... ["UNITS3_9", "N", 8, 0], ["UNITS10_49", "N", 8, 0], ... ["UNITS50_UP", "N", 8, 0], ["MOBILEHOME", "N", 7, 0]] -The first field of a dbf file is always a 1-byte field called "DeletionFlag", -which indicates records that have been deleted but not removed. However, -since this flag is very rarely used, PyShp currently will return all records -regardless of their deletion flag, and the flag is also not included in the list of -record values. In other words, the DeletionFlag field has no real purpose, and +The first field of a dbf file is always a 1-byte field called "DeletionFlag", +which indicates records that have been deleted but not removed. However, +since this flag is very rarely used, PyShp currently will return all records +regardless of their deletion flag, and the flag is also not included in the list of +record values. In other words, the DeletionFlag field has no real purpose, and should in most cases be ignored. For instance, to get a list of all fieldnames: @@ -593,10 +593,10 @@ To read a single record call the record() method with the record's index: >>> rec = sf.record(3) - + Each record is a list-like Record object containing the values corresponding to each field in the field list (except the DeletionFlag). A record's values can be accessed by positional indexing or slicing. -For example in the blockgroups shapefile the 2nd and 3rd fields are the blockgroup id +For example in the blockgroups shapefile the 2nd and 3rd fields are the blockgroup id and the 1990 population count of that San Francisco blockgroup: @@ -604,7 +604,7 @@ and the 1990 population count of that San Francisco blockgroup: ['060750601001', 4715] For simpler access, the fields of a record can also accessed via the name of the field, -either as a key or as an attribute name. The blockgroup id (BKG_KEY) of the blockgroups shapefile +either as a key or as an attribute name. The blockgroup id (BKG_KEY) of the blockgroups shapefile can also be retrieved as: @@ -613,7 +613,7 @@ can also be retrieved as: >>> rec.BKG_KEY '060750601001' - + The record values can be easily integrated with other programs by converting it to a field-value dictionary: @@ -621,13 +621,13 @@ The record values can be easily integrated with other programs by converting it >>> sorted(dct.items()) [('AGE_18_29', 1467), ('AGE_30_49', 1681), ('AGE_50_64', 92), ('AGE_5_17', 848), ('AGE_65_UP', 30), ('AGE_UNDER5', 597), ('AMERI_ES', 6), ('AREA', 2.34385), ('ASIAN_PI', 452), ('BKG_KEY', '060750601001'), ('BLACK', 1007), ('DIVORCED', 149), ('FEMALES', 2095), ('FHH_CHILD', 16), ('HISPANIC', 416), ('HOUSEHOLDS', 1195), ('HSEHLD_1_F', 40), ('HSEHLD_1_M', 22), ('HSE_UNITS', 1258), ('MALES', 2620), ('MARHH_CHD', 79), ('MARHH_NO_C', 958), ('MARRIED', 2021), ('MEDIANRENT', 739), ('MEDIAN_VAL', 337500), ('MHH_CHILD', 0), ('MOBILEHOME', 0), ('NEVERMARRY', 703), ('OTHER', 288), ('OWNER_OCC', 66), ('POP1990', 4715), ('POP90_SQMI', 2011.6), ('RENTER_OCC', 3733), ('SEPARATED', 49), ('UNITS10_49', 49), ('UNITS2', 160), ('UNITS3_9', 672), ('UNITS50_UP', 0), ('UNITS_1ATT', 302), ('UNITS_1DET', 43), ('VACANT', 93), ('WHITE', 2962), ('WIDOWED', 37)] -If at a later point you need to check the record's index position in the original +If at a later point you need to check the record's index position in the original shapefile, you can do this through the "oid" attribute: >>> rec.oid 3 - + ### Reading Geometry and Records Simultaneously You may want to examine both the geometry and the attributes for a record at @@ -663,13 +663,13 @@ To get the 4th shape record from the blockgroups shapefile use the third index: >>> shapeRec = sf.shapeRecord(3) >>> shapeRec.record[1:3] ['060750601001', 4715] - + Each individual shape record also supports the _\_geo_interface\_\_ to convert it to a GeoJSON feature: >>> shapeRec.__geo_interface__['type'] 'Feature' - + ## Writing Shapefiles @@ -697,7 +697,7 @@ the file path and name to save to: >>> w = shapefile.Writer('shapefiles/test/testfile') >>> w.field('field1', 'C') - + File extensions are optional when reading or writing shapefiles. If you specify them PyShp ignores them anyway. When you save files you can specify a base file name that is used for all three file types. Or you can specify a name for @@ -706,9 +706,9 @@ one or more file types: >>> w = shapefile.Writer(dbf='shapefiles/test/onlydbf.dbf') >>> w.field('field1', 'C') - + In that case, any file types not assigned will not -save and only file types with file names will be saved. +save and only file types with file names will be saved. #### Writing Shapefiles to File-Like Objects @@ -738,14 +738,14 @@ write to them: >>> r = shapefile.Reader(shp=shp, shx=shx, dbf=dbf) >>> len(r) 1 - - + + #### Writing Shapefiles Using the Context Manager The "Writer" class automatically closes the open files and writes the final headers once it is garbage collected. -In case of a crash and to make the code more readable, it is nevertheless recommended -you do this manually by calling the "close()" method: +In case of a crash and to make the code more readable, it is nevertheless recommended +you do this manually by calling the "close()" method: >>> w.close() @@ -757,15 +757,15 @@ objects are properly closed and final headers written once you exit the with-cla >>> with shapefile.Writer("shapefiles/test/contextwriter") as w: ... w.field('field1', 'C') ... pass - + #### Setting the Shape Type The shape type defines the type of geometry contained in the shapefile. All of the shapes must match the shape type setting. -There are three ways to set the shape type: - * Set it when creating the class instance. - * Set it by assigning a value to an existing class instance. +There are three ways to set the shape type: + * Set it when creating the class instance. + * Set it by assigning a value to an existing class instance. * Set it automatically to the type of the first non-null shape by saving the shapefile. To manually set the shape type for a Writer object when creating the Writer: @@ -784,14 +784,14 @@ OR you can set it after the Writer is created: >>> w.shapeType 1 - + ### Adding Records -Before you can add records you must first create the fields that define what types of -values will go into each attribute. +Before you can add records you must first create the fields that define what types of +values will go into each attribute. -There are several different field types, all of which support storing None values as NULL. +There are several different field types, all of which support storing None values as NULL. Text fields are created using the 'C' type, and the third 'size' argument can be customized to the expected length of text values to save space: @@ -804,12 +804,12 @@ length of text values to save space: >>> w.null() >>> w.record('Hello', 'World', 'World'*50) >>> w.close() - + >>> r = shapefile.Reader('shapefiles/test/dtype') >>> assert r.record(0) == ['Hello', 'World', 'World'*50] -Date fields are created using the 'D' type, and can be created using either -date objects, lists, or a YYYYMMDD formatted string. +Date fields are created using the 'D' type, and can be created using either +date objects, lists, or a YYYYMMDD formatted string. Field length or decimal have no impact on this type: @@ -825,18 +825,18 @@ Field length or decimal have no impact on this type: >>> w.record('19980130') >>> w.record(None) >>> w.close() - + >>> r = shapefile.Reader('shapefiles/test/dtype') >>> assert r.record(0) == [date(1898,1,30)] >>> assert r.record(1) == [date(1998,1,30)] >>> assert r.record(2) == [date(1998,1,30)] >>> assert r.record(3) == [None] -Numeric fields are created using the 'N' type (or the 'F' type, which is exactly the same). -By default the fourth decimal argument is set to zero, essentially creating an integer field. -To store floats you must set the decimal argument to the precision of your choice. -To store very large numbers you must increase the field length size to the total number of digits -(including comma and minus). +Numeric fields are created using the 'N' type (or the 'F' type, which is exactly the same). +By default the fourth decimal argument is set to zero, essentially creating an integer field. +To store floats you must set the decimal argument to the precision of your choice. +To store very large numbers you must increase the field length size to the total number of digits +(including comma and minus). >>> w = shapefile.Writer('shapefiles/test/dtype') @@ -852,15 +852,15 @@ To store very large numbers you must increase the field length size to the total >>> w.record(INT=nr, LOWPREC=nr, MEDPREC=nr, HIGHPREC=-3.2302e-25, FTYPE=nr, LARGENR=int(nr)*10**100) >>> w.record(None, None, None, None, None, None) >>> w.close() - + >>> r = shapefile.Reader('shapefiles/test/dtype') >>> assert r.record(0) == [1, 1.32, 1.3217328, -3.2302e-25, 1.3217328, 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000] >>> assert r.record(1) == [None, None, None, None, None, None] - -Finally, we can create boolean fields by setting the type to 'L'. -This field can take True or False values, or 1 (True) or 0 (False). -None is interpreted as missing. + +Finally, we can create boolean fields by setting the type to 'L'. +This field can take True or False values, or 1 (True) or 0 (False). +None is interpreted as missing. >>> w = shapefile.Writer('shapefiles/test/dtype') @@ -878,7 +878,7 @@ None is interpreted as missing. >>> w.record(None) >>> w.record("Nonsense") >>> w.close() - + >>> r = shapefile.Reader('shapefiles/test/dtype') >>> r.record(0) Record #0: [True] @@ -892,7 +892,7 @@ None is interpreted as missing. Record #4: [None] >>> r.record(5) Record #5: [None] - + You can also add attributes using keyword arguments where the keys are field names. @@ -909,12 +909,12 @@ You can also add attributes using keyword arguments where the keys are field nam Geometry is added using one of several convenience methods. The "null" method is used for null shapes, "point" is used for point shapes, "multipoint" is used for multipoint shapes, "line" for lines, -"poly" for polygons. +"poly" for polygons. **Adding a Null shape** -A shapefile may contain some records for which geometry is not available, and may be set using the "null" method. -Because Null shape types (shape type 0) have no geometry the "null" method is called without any arguments. +A shapefile may contain some records for which geometry is not available, and may be set using the "null" method. +Because Null shape types (shape type 0) have no geometry the "null" method is called without any arguments. >>> w = shapefile.Writer('shapefiles/test/null') @@ -928,59 +928,59 @@ Because Null shape types (shape type 0) have no geometry the "null" method is ca **Adding a Point shape** Point shapes are added using the "point" method. A point is specified by an x and -y value. +y value. >>> w = shapefile.Writer('shapefiles/test/point') >>> w.field('name', 'C') - - >>> w.point(122, 37) + + >>> w.point(122, 37) >>> w.record('point1') - + >>> w.close() **Adding a MultiPoint shape** -If your point data allows for the possibility of multiple points per feature, use "multipoint" instead. -These are specified as a list of xy point coordinates. +If your point data allows for the possibility of multiple points per feature, use "multipoint" instead. +These are specified as a list of xy point coordinates. >>> w = shapefile.Writer('shapefiles/test/multipoint') >>> w.field('name', 'C') - - >>> w.multipoint([[122,37], [124,32]]) + + >>> w.multipoint([[122,37], [124,32]]) >>> w.record('multipoint1') - + >>> w.close() - + **Adding a LineString shape** -For LineString shapefiles, each shape is given as a list of one or more linear features. -Each of the linear features must have at least two points. - - +For LineString shapefiles, each shape is given as a list of one or more linear features. +Each of the linear features must have at least two points. + + >>> w = shapefile.Writer('shapefiles/test/line') >>> w.field('name', 'C') - + >>> w.line([ ... [[1,5],[5,5],[5,1],[3,3],[1,1]], # line 1 ... [[3,2],[2,6]] # line 2 ... ]) - + >>> w.record('linestring1') - + >>> w.close() - + **Adding a Polygon shape** Similarly to LineString, Polygon shapes consist of multiple polygons, and must be given as a list of polygons. -The main difference is that polygons must have at least 4 points and the last point must be the same as the first. +The main difference is that polygons must have at least 4 points and the last point must be the same as the first. It's also okay if you forget to repeat the first point at the end; PyShp automatically checks and closes the polygons if you don't. It's important to note that for Polygon shapefiles, your polygon coordinates must be ordered in a clockwise direction. If any of the polygons have holes, then the hole polygon coordinates must be ordered in a counterclockwise direction. -The direction of your polygons determines how shapefile readers will distinguish between polygon outlines and holes. +The direction of your polygons determines how shapefile readers will distinguish between polygon outlines and holes. >>> w = shapefile.Writer('shapefiles/test/polygon') @@ -992,13 +992,13 @@ The direction of your polygons determines how shapefile readers will distinguish ... [[15,2], [17,6], [22,7]] # poly 2 ... ]) >>> w.record('polygon1') - + >>> w.close() - + **Adding from an existing Shape object** Finally, geometry can be added by passing an existing "Shape" object to the "shape" method. -You can also pass it any GeoJSON dictionary or _\_geo_interface\_\_ compatible object. +You can also pass it any GeoJSON dictionary or _\_geo_interface\_\_ compatible object. This can be particularly useful for copying from one file to another: @@ -1011,14 +1011,14 @@ This can be particularly useful for copying from one file to another: >>> for shaperec in r.iterShapeRecords(): ... w.record(*shaperec.record) ... w.shape(shaperec.shape) - + >>> # or GeoJSON dicts >>> for shaperec in r.iterShapeRecords(): ... w.record(*shaperec.record) ... w.shape(shaperec.shape.__geo_interface__) - - >>> w.close() - + + >>> w.close() + ### Geometry and Record Balancing @@ -1027,17 +1027,17 @@ number of records equals the number of shapes to create a valid shapefile. You must take care to add records and shapes in the same order so that the record data lines up with the geometry data. For example: - + >>> w = shapefile.Writer('shapefiles/test/balancing', shapeType=shapefile.POINT) >>> w.field("field1", "C") >>> w.field("field2", "C") - + >>> w.record("row", "one") >>> w.point(1, 1) - + >>> w.record("row", "two") >>> w.point(2, 2) - + To help prevent accidental misalignment PyShp has an "auto balance" feature to make sure when you add either a shape or a record the two sides of the equation line up. This way if you forget to update an entry the @@ -1050,7 +1050,7 @@ the attribute autoBalance to 1 or True: >>> w.record("row", "three") >>> w.record("row", "four") >>> w.point(4, 4) - + >>> w.recNum == w.shpNum True @@ -1059,7 +1059,7 @@ to ensure the other side is up to date. When balancing is used null shapes are created on the geometry side or records with a value of "NULL" for each field is created on the attribute side. This gives you flexibility in how you build the shapefile. -You can create all of the shapes and then create all of the records or vice versa. +You can create all of the shapes and then create all of the records or vice versa. >>> w.autoBalance = 0 @@ -1069,16 +1069,16 @@ You can create all of the shapes and then create all of the records or vice vers >>> w.point(5, 5) >>> w.point(6, 6) >>> w.balance() - + >>> w.recNum == w.shpNum True If you do not use the autoBalance() or balance() method and forget to manually balance the geometry and attributes the shapefile will be viewed as corrupt by most shapefile software. - + ### Writing .prj files -A .prj file, or projection file, is a simple text file that stores a shapefile's map projection and coordinate reference system to help mapping software properly locate the geometry on a map. If you don't have one, you may get confusing errors when you try and use the shapefile you created. The GIS software may complain that it doesn't know the shapefile's projection and refuse to accept it, it may assume the shapefile is the same projection as the rest of your GIS project and put it in the wrong place, or it might assume the coordinates are an offset in meters from latitude and longitude 0,0 which will put your data in the middle of the ocean near Africa. The text in the .prj file is a [Well-Known-Text (WKT) projection string](https://en.wikipedia.org/wiki/Well-known_text_representation_of_coordinate_reference_systems). Projection strings can be quite long so they are often referenced using numeric codes call EPSG codes. The .prj file must have the same base name as your shapefile. So for example if you have a shapefile named "myPoints.shp", the .prj file must be named "myPoints.prj". +A .prj file, or projection file, is a simple text file that stores a shapefile's map projection and coordinate reference system to help mapping software properly locate the geometry on a map. If you don't have one, you may get confusing errors when you try and use the shapefile you created. The GIS software may complain that it doesn't know the shapefile's projection and refuse to accept it, it may assume the shapefile is the same projection as the rest of your GIS project and put it in the wrong place, or it might assume the coordinates are an offset in meters from latitude and longitude 0,0 which will put your data in the middle of the ocean near Africa. The text in the .prj file is a [Well-Known-Text (WKT) projection string](https://en.wikipedia.org/wiki/Well-known_text_representation_of_coordinate_reference_systems). Projection strings can be quite long so they are often referenced using numeric codes call EPSG codes. The .prj file must have the same base name as your shapefile. So for example if you have a shapefile named "myPoints.shp", the .prj file must be named "myPoints.prj". If you're using the same projection over and over, the following is a simple way to create the .prj file assuming your base filename is stored in a variable called "filename": @@ -1092,17 +1092,17 @@ If you're using the same projection over and over, the following is a simple way prj.write(wkt) ``` -If you need to dynamically fetch WKT projection strings, you can use the pure Python [PyCRS](https://github.com/karimbahgat/PyCRS) module which has a number of useful features. +If you need to dynamically fetch WKT projection strings, you can use the pure Python [PyCRS](https://github.com/karimbahgat/PyCRS) module which has a number of useful features. # Advanced Use ## Common Errors and Fixes -Below we list some commonly encountered errors and ways to fix them. +Below we list some commonly encountered errors and ways to fix them. ### Warnings and Logging -By default, PyShp chooses to be transparent and provide the user with logging information and warnings about non-critical issues when reading or writing shapefiles. This behavior is controlled by the module constant `VERBOSE` (which defaults to True). If you would rather suppress this information, you can simply set this to False: +By default, PyShp chooses to be transparent and provide the user with logging information and warnings about non-critical issues when reading or writing shapefiles. This behavior is controlled by the module constant `VERBOSE` (which defaults to True). If you would rather suppress this information, you can simply set this to False: >>> shapefile.VERBOSE = False @@ -1115,21 +1115,21 @@ All logging happens under the namespace `shapefile`. So another way to suppress ### Shapefile Encoding Errors -PyShp supports reading and writing shapefiles in any language or character encoding, and provides several options for decoding and encoding text. -Most shapefiles are written in UTF-8 encoding, PyShp's default encoding, so in most cases you don't have to specify the encoding. -If you encounter an encoding error when reading a shapefile, this means the shapefile was likely written in a non-utf8 encoding. +PyShp supports reading and writing shapefiles in any language or character encoding, and provides several options for decoding and encoding text. +Most shapefiles are written in UTF-8 encoding, PyShp's default encoding, so in most cases you don't have to specify the encoding. +If you encounter an encoding error when reading a shapefile, this means the shapefile was likely written in a non-utf8 encoding. For instance, when working with English language shapefiles, a common reason for encoding errors is that the shapefile was written in Latin-1 encoding. -For reading shapefiles in any non-utf8 encoding, such as Latin-1, just -supply the encoding option when creating the Reader class. +For reading shapefiles in any non-utf8 encoding, such as Latin-1, just +supply the encoding option when creating the Reader class. >>> r = shapefile.Reader("shapefiles/test/latin1.shp", encoding="latin1") >>> r.record(0) == [2, u'ÑandĂș'] True - -Once you have loaded the shapefile, you may choose to save it using another more supportive encoding such -as UTF-8. Assuming the new encoding supports the characters you are trying to write, reading it back in -should give you the same unicode string you started with. + +Once you have loaded the shapefile, you may choose to save it using another more supportive encoding such +as UTF-8. Assuming the new encoding supports the characters you are trying to write, reading it back in +should give you the same unicode string you started with. >>> w = shapefile.Writer("shapefiles/test/latin_as_utf8.shp", encoding="utf8") @@ -1137,15 +1137,15 @@ should give you the same unicode string you started with. >>> w.record(*r.record(0)) >>> w.null() >>> w.close() - + >>> r = shapefile.Reader("shapefiles/test/latin_as_utf8.shp", encoding="utf8") >>> r.record(0) == [2, u'ÑandĂș'] True - + If you supply the wrong encoding and the string is unable to be decoded, PyShp will by default raise an exception. If however, on rare occasion, you are unable to find the correct encoding and want to ignore or replace encoding errors, you can specify the "encodingErrors" to be used by the decode method. This -applies to both reading and writing. +applies to both reading and writing. >>> r = shapefile.Reader("shapefiles/test/latin1.shp", encoding="ascii", encodingErrors="replace") @@ -1156,8 +1156,8 @@ applies to both reading and writing. ## Reading Large Shapefiles -Despite being a lightweight library, PyShp is designed to be able to read shapefiles of any size, allowing you to work with hundreds of thousands or even millions -of records and complex geometries. +Despite being a lightweight library, PyShp is designed to be able to read shapefiles of any size, allowing you to work with hundreds of thousands or even millions +of records and complex geometries. ### Iterating through a shapefile @@ -1167,22 +1167,22 @@ As an example, let's load this Natural Earth shapefile of more than 4000 global >>> sf = shapefile.Reader("https://github.com/nvkelso/natural-earth-vector/blob/master/10m_cultural/ne_10m_admin_1_states_provinces?raw=true") When first creating the Reader class, the library only reads the header information -and leaves the rest of the file contents alone. Once you call the records() and shapes() -methods however, it will attempt to read the entire file into memory at once. +and leaves the rest of the file contents alone. Once you call the records() and shapes() +methods however, it will attempt to read the entire file into memory at once. For very large files this can result in MemoryError. So when working with large files it is recommended to use instead the iterShapes(), iterRecords(), or iterShapeRecords() -methods instead. These iterate through the file contents one at a time, enabling you to loop -through them while keeping memory usage at a minimum. +methods instead. These iterate through the file contents one at a time, enabling you to loop +through them while keeping memory usage at a minimum. >>> for shape in sf.iterShapes(): ... # do something here ... pass - + >>> for rec in sf.iterRecords(): ... # do something here ... pass - + >>> for shapeRec in sf.iterShapeRecords(): ... # do something here ... pass @@ -1202,7 +1202,7 @@ By default when reading the attribute records of a shapefile, pyshp unpacks and ... pass >>> rec Record #4595: ['Birgu', 'Malta'] - + ### Attribute filtering In many cases, we aren't interested in all entries of a shapefile, but rather only want to retrieve a small subset of records by filtering on some attribute. To avoid wasting time reading records and shapes that we don't need, we can start by iterating only the records and fields of interest, check if the record matches some condition as a way to filter the data, and finally load the full record and shape geometry for those that meet the condition: @@ -1222,7 +1222,7 @@ In many cases, we aren't interested in all entries of a shapefile, but rather on 'Maekel' 'Anseba' -Selectively reading only the necessary data in this way is particularly useful for efficiently processing a limited subset of data from very large files or when looping through a large number of files, especially if they contain large attribute tables or complex shape geometries. +Selectively reading only the necessary data in this way is particularly useful for efficiently processing a limited subset of data from very large files or when looping through a large number of files, especially if they contain large attribute tables or complex shape geometries. ### Spatial filtering @@ -1253,23 +1253,23 @@ Another common use-case is that we only want to read those records that are loca Record #2037: ['Al Hudaydah', 'Yemen'] Record #3741: ['Anseba', 'Eritrea'] -This functionality means that shapefiles can be used as a bare-bones spatially indexed database, with very fast bounding box queries for even the largest of shapefiles. Note that, as with all spatial indexing, this method does not guarantee that the *geometries* of the resulting matches overlap the queried region, only that their *bounding boxes* overlap. +This functionality means that shapefiles can be used as a bare-bones spatially indexed database, with very fast bounding box queries for even the largest of shapefiles. Note that, as with all spatial indexing, this method does not guarantee that the *geometries* of the resulting matches overlap the queried region, only that their *bounding boxes* overlap. ## Writing large shapefiles -Similar to the Reader class, the shapefile Writer class uses a streaming approach to keep memory -usage at a minimum and allow writing shapefiles of arbitrarily large sizes. The library takes care of this under-the-hood by immediately -writing each geometry and record to disk the moment they -are added using shape() or record(). Once the writer is closed, exited, or garbage -collected, the final header information is calculated and written to the beginning of -the file. +Similar to the Reader class, the shapefile Writer class uses a streaming approach to keep memory +usage at a minimum and allow writing shapefiles of arbitrarily large sizes. The library takes care of this under-the-hood by immediately +writing each geometry and record to disk the moment they +are added using shape() or record(). Once the writer is closed, exited, or garbage +collected, the final header information is calculated and written to the beginning of +the file. ### Merging multiple shapefiles -This means that it's possible to merge hundreds or thousands of shapefiles, as -long as you iterate through the source files to avoid loading everything into +This means that it's possible to merge hundreds or thousands of shapefiles, as +long as you iterate through the source files to avoid loading everything into memory. The following example copies the contents of a shapefile to a new file 10 times: >>> # create writer @@ -1295,12 +1295,12 @@ memory. The following example copies the contents of a shapefile to a new file 1 >>> # close the writer >>> w.close() -In this trivial example, we knew that all files had the exact same field names, ordering, and types. In other scenarios, you will have to additionally make sure that all shapefiles have the exact same fields in the same order, and that they all contain the same geometry type. +In this trivial example, we knew that all files had the exact same field names, ordering, and types. In other scenarios, you will have to additionally make sure that all shapefiles have the exact same fields in the same order, and that they all contain the same geometry type. ### Editing shapefiles -If you need to edit a shapefile you would have to read the -file one record at a time, modify or filter the contents, and write it back out. For instance, to create a copy of a shapefile that only keeps a subset of relevant fields: +If you need to edit a shapefile you would have to read the +file one record at a time, modify or filter the contents, and write it back out. For instance, to create a copy of a shapefile that only keeps a subset of relevant fields: >>> # create writer >>> w = shapefile.Writer('shapefiles/test/edit') @@ -1325,7 +1325,7 @@ file one record at a time, modify or filter the contents, and write it back out. ## 3D and Other Geometry Types Most shapefiles store conventional 2D points, lines, or polygons. But the shapefile format is also capable -of storing various other types of geometries as well, including complex 3D surfaces and objects. +of storing various other types of geometries as well, including complex 3D surfaces and objects. ### Shapefiles with measurement (M) values @@ -1338,107 +1338,107 @@ or by simply omitting the third M-coordinate. >>> w = shapefile.Writer('shapefiles/test/linem') >>> w.field('name', 'C') - + >>> w.linem([ ... [[1,5,0],[5,5],[5,1,3],[3,3,None],[1,1,0]], # line with one omitted and one missing M-value ... [[3,2],[2,6]] # line without any M-values ... ]) - + >>> w.record('linem1') - + >>> w.close() - + Shapefiles containing M-values can be examined in several ways: >>> r = shapefile.Reader('shapefiles/test/linem') - + >>> r.mbox # the lower and upper bound of M-values in the shapefile [0.0, 3.0] - + >>> r.shape(0).m # flat list of M-values [0.0, None, 3.0, None, 0.0, None, None] - + ### Shapefiles with elevation (Z) values -Elevation shape types are shapes that include an elevation value at each vertex, for instance elevation from a GPS device. -Shapes with elevation (Z) values are added with the following methods: "pointz", "multipointz", "linez", and "polyz". +Elevation shape types are shapes that include an elevation value at each vertex, for instance elevation from a GPS device. +Shapes with elevation (Z) values are added with the following methods: "pointz", "multipointz", "linez", and "polyz". The Z-values are specified by adding a third Z value to each XY coordinate. Z-values do not support the concept of missing data, but if you omit the third Z-coordinate it will default to 0. Note that Z-type shapes also support measurement (M) values added -as a fourth M-coordinate. This too is optional. - - +as a fourth M-coordinate. This too is optional. + + >>> w = shapefile.Writer('shapefiles/test/linez') >>> w.field('name', 'C') - + >>> w.linez([ ... [[1,5,18],[5,5,20],[5,1,22],[3,3],[1,1]], # line with some omitted Z-values ... [[3,2],[2,6]], # line without any Z-values ... [[3,2,15,0],[2,6,13,3],[1,9,14,2]] # line with both Z- and M-values ... ]) - + >>> w.record('linez1') - + >>> w.close() - + To examine a Z-type shapefile you can do: >>> r = shapefile.Reader('shapefiles/test/linez') - + >>> r.zbox # the lower and upper bound of Z-values in the shapefile [0.0, 22.0] - + >>> r.shape(0).z # flat list of Z-values [18.0, 20.0, 22.0, 0.0, 0.0, 0.0, 0.0, 15.0, 13.0, 14.0] ### 3D MultiPatch Shapefiles -Multipatch shapes are useful for storing composite 3-Dimensional objects. +Multipatch shapes are useful for storing composite 3-Dimensional objects. A MultiPatch shape represents a 3D object made up of one or more surface parts. Each surface in "parts" is defined by a list of XYZM values (Z and M values optional), and its corresponding type is -given in the "partTypes" argument. The part type decides how the coordinate sequence is to be interpreted, and can be one +given in the "partTypes" argument. The part type decides how the coordinate sequence is to be interpreted, and can be one of the following module constants: TRIANGLE_STRIP, TRIANGLE_FAN, OUTER_RING, INNER_RING, FIRST_RING, or RING. -For instance, a TRIANGLE_STRIP may be used to represent the walls of a building, combined with a TRIANGLE_FAN to represent -its roof: +For instance, a TRIANGLE_STRIP may be used to represent the walls of a building, combined with a TRIANGLE_FAN to represent +its roof: >>> from shapefile import TRIANGLE_STRIP, TRIANGLE_FAN - + >>> w = shapefile.Writer('shapefiles/test/multipatch') >>> w.field('name', 'C') - + >>> w.multipatch([ ... [[0,0,0],[0,0,3],[5,0,0],[5,0,3],[5,5,0],[5,5,3],[0,5,0],[0,5,3],[0,0,0],[0,0,3]], # TRIANGLE_STRIP for house walls ... [[2.5,2.5,5],[0,0,3],[5,0,3],[5,5,3],[0,5,3],[0,0,3]], # TRIANGLE_FAN for pointed house roof ... ], ... partTypes=[TRIANGLE_STRIP, TRIANGLE_FAN]) # one type for each part - + >>> w.record('house1') - + >>> w.close() - + For an introduction to the various multipatch part types and examples of how to create 3D MultiPatch objects see [this -ESRI White Paper](http://downloads.esri.com/support/whitepapers/ao_/J9749_MultiPatch_Geometry_Type.pdf). +ESRI White Paper](http://downloads.esri.com/support/whitepapers/ao_/J9749_MultiPatch_Geometry_Type.pdf). + - # Testing -The testing framework is pytest, and the tests are located in test_shapefile.py. -This includes an extensive set of unit tests of the various pyshp features, -and tests against various input data. Some of the tests that require -internet connectivity will be skipped in offline testing environments. -In the same folder as README.md and shapefile.py, from the command line run +The testing framework is pytest, and the tests are located in test_shapefile.py. +This includes an extensive set of unit tests of the various pyshp features, +and tests against various input data. Some of the tests that require +internet connectivity will be skipped in offline testing environments. +In the same folder as README.md and shapefile.py, from the command line run ``` $ python -m pytest -``` +``` -Additionally, all the code and examples located in this file, README.md, +Additionally, all the code and examples located in this file, README.md, is tested and verified with the builtin doctest framework. A special routine for invoking the doctest is run when calling directly on shapefile.py. -In the same folder as README.md and shapefile.py, from the command line run +In the same folder as README.md and shapefile.py, from the command line run ``` $ python shapefile.py -``` +``` Linux/Mac and similar platforms will need to run `$ dos2unix README.md` in order to correct line endings in README.md. diff --git a/shapefile.py b/shapefile.py index cace43f..b218103 100644 --- a/shapefile.py +++ b/shapefile.py @@ -9,15 +9,15 @@ __version__ = "2.3.1" import array -from datetime import date import io import logging import os -from struct import pack, unpack, calcsize, error, Struct import sys import tempfile import time import zipfile +from datetime import date +from struct import Struct, calcsize, error, pack, unpack # Create named logger logger = logging.getLogger(__name__) @@ -81,16 +81,15 @@ xrange = range izip = zip - from urllib.parse import urlparse, urlunparse from urllib.error import HTTPError - from urllib.request import urlopen, Request + from urllib.parse import urlparse, urlunparse + from urllib.request import Request, urlopen else: from itertools import izip + from urllib2 import HTTPError, Request, urlopen from urlparse import urlparse, urlunparse - from urllib2 import HTTPError - from urllib2 import urlopen, Request # Helpers diff --git a/test_shapefile.py b/test_shapefile.py index ad7ea08..ca1e92d 100644 --- a/test_shapefile.py +++ b/test_shapefile.py @@ -6,11 +6,13 @@ import json import os.path import sys + if sys.version_info.major == 3: from pathlib import Path # third party imports import pytest + if sys.version_info.major == 2: # required by pytest for python <36 from pathlib2 import Path @@ -18,7 +20,6 @@ # our imports import shapefile - # define various test shape tuples of (type, points, parts indexes, and expected geo interface output) geo_interface_tests = [ (shapefile.POINT, # point [(1,1)],